@nyuchi/mzizi-mcp 0.11.2 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/README.md +20 -12
  2. package/dist/auth/authkit-handler.d.ts +1 -1
  3. package/dist/auth/authkit-handler.d.ts.map +1 -1
  4. package/dist/auth/authkit-handler.js +34 -16
  5. package/dist/auth/authkit-handler.js.map +1 -1
  6. package/dist/auth/authorize-request.d.ts +12 -0
  7. package/dist/auth/authorize-request.d.ts.map +1 -0
  8. package/dist/auth/authorize-request.js +37 -0
  9. package/dist/auth/authorize-request.js.map +1 -0
  10. package/dist/auth/id-token.d.ts +28 -0
  11. package/dist/auth/id-token.d.ts.map +1 -0
  12. package/dist/auth/id-token.js +32 -0
  13. package/dist/auth/id-token.js.map +1 -0
  14. package/dist/env.d.ts.map +1 -1
  15. package/dist/fundi-client.d.ts +6 -6
  16. package/dist/fundi-client.js +6 -6
  17. package/dist/generated/registry-pin.d.ts +1 -1
  18. package/dist/generated/registry-pin.js +1 -1
  19. package/dist/generated/registry.d.ts +12 -5
  20. package/dist/generated/registry.d.ts.map +1 -1
  21. package/dist/generated/registry.js +421 -164
  22. package/dist/generated/registry.js.map +1 -1
  23. package/dist/generated/skills.d.ts +5 -1
  24. package/dist/generated/skills.d.ts.map +1 -1
  25. package/dist/generated/skills.js +32 -11
  26. package/dist/generated/skills.js.map +1 -1
  27. package/dist/identity.d.ts +35 -21
  28. package/dist/identity.d.ts.map +1 -1
  29. package/dist/identity.js +70 -29
  30. package/dist/identity.js.map +1 -1
  31. package/dist/registry.d.ts +32 -3
  32. package/dist/registry.d.ts.map +1 -1
  33. package/dist/registry.js +36 -3
  34. package/dist/registry.js.map +1 -1
  35. package/dist/server.d.ts +1 -1
  36. package/dist/server.js +1 -1
  37. package/dist/tools.d.ts.map +1 -1
  38. package/dist/tools.js +57 -18
  39. package/dist/tools.js.map +1 -1
  40. package/package.json +2 -2
@@ -6,38 +6,59 @@
6
6
  * changes a SKILL.md redeploys the Worker, and the Worker is what answers
7
7
  * `mzizi_get_skills`.
8
8
  */
9
+ /** Skill categories, from mzizi-skills/index.schema.json. */
10
+ export const SKILL_CATEGORIES = ["dev", "design", "language", "build"];
9
11
  export const BUNDLED_SKILLS = [
10
12
  {
11
13
  "name": "mzizi-language",
12
- "description": "Write and check Mzizi language source (.mz): the component syntax (enum tables, props, the view tree, when / for each, records, use motion), the mz check --agent loop and its NDJSON diagnostics, mz fix for every exact fix, mz contract for the contract … end block (RFC-0006, RFC-0010), the diagnostic codes you will meet (MZ0101, MZ0106, MZ0312, MZ0313, MZ0406–MZ0409, MZ06xx, MZ07xx), and what is not built yet: lowering to Rust, a runtime, handlers, a release.",
13
- "body_mdx": "# Writing and checking Mzizi\n\nMzizi is a language whose syntax, type system and compiler feedback loop are designed for\nmachine authorship. The language lives in [`mzizi-dev/mzizi`](https://github.com/mzizi-dev/mzizi)\n(public). It is **not** the component registry (`mzizi-dev/mzizi-registry`); that is the\ncompanion `mzizi-roots` skill.\n\nThis skill describes `main` at `e9e9233` (2026-09-29). Where this skill and the code\ndisagree, **the code is the fact** (RFC-0003 §7.1). Every example below is copied from a file\nin the repository that passes `mz check` and `mz contract` there.\n\n## What exists, and what does not\n\n**Built and tested:** the lexer, the recovering parser, the name and type resolver, the agent\ndiagnostic protocol (`mz check --agent`), `mz fix`, the content-addressed IR, `mz outline`,\ncontract evaluation (`mz contract`), nine primitives written in Mzizi (`primitives/*.mz`), two\nexamples (`examples/*.mz`) and the Phase 0 benchmark harness and runner. All of it is gated in\nCI.\n\n**Not built:** lowering to Rust, code generation, a runtime, rendering, a release and a\npublished binary. A `.mz` file today is checked and its contract evaluated; **nothing runs\nit.** Do not tell anyone Mzizi \"compiles to Rust\" or is \"production ready\".\n\n**In progress, as design only:**\n\n- **Handlers and the backend slice.** RFC-0011 (handlers, the backend measurement slice) is a\n draft on a branch, not on `main`. RFC-0007 G2.2 (handler declarations) and G1.2 (function\n parameters and bodies) are not implemented. There is no `route`, `service` or HTTP syntax\n you can check today.\n- **Contracts everywhere.** RFC-0010 (draft) extends contracts to functions, handlers,\n services and records, with codes `MZ0606`–`MZ0613`. **None of those codes exist in the\n compiler yet.** Only component contracts (RFC-0006) are evaluated.\n- **Modules.** There are no imports. Components are referenced by bare name\n (`primitives/confirm_bar.mz` uses `button` and `alert` with no import line). A reference to\n another component's enum (`button_variant.ghost`) is warning `MZ0502`, because it cannot be\n checked until modules exist.\n\n**Results, as they fell.** Two Phase 0 pilots ran on 2026-09-27 and neither showed an\nadvantage for Mzizi. On the frontier model the Mzizi and Dioxus arms tied, with Mzizi about 8%\nfewer transcript tokens. On the ~7B open-weight model, the design target, Mzizi did worse on\nall three metrics. Neither pilot is the kill-criterion run (RFC-0009 §6), and that run has not\nhappened. \"Designed for agents\" is accurate; \"faster\" or \"better\" is not.\n\n## Getting `mz`\n\nThere is no release. Build it from source; the compiler crate has zero dependencies:\n\n```bash\ngit clone https://github.com/mzizi-dev/mzizi.git && cd mzizi/compiler\ncargo build --bin mz # ../target/debug/mz\ncargo run --bin mz -- check ../primitives/button.mz\n```\n\n**Never `cargo install mz`.** The `mz` crate on crates.io is an unrelated project, and the\ncompiler crate (`mzizi-lang-compiler`) is `publish = false`.\n\n## The commands\n\n`mz` takes exactly one file per call. `mz check ../primitives/*.mz` is a usage error (exit 2).\nLoop over files instead.\n\n| Command | Does |\n| --------------------------------- | -------------------------------------------------------------------------------- |\n| `mz check <file>` | human-readable diagnostics |\n| `mz check --agent <file>` | NDJSON: one diagnostic per line, then one summary line. **Target this.** |\n| `mz fix <file>` | applies every `exact` fix in place, in one pass, then re-checks the file |\n| `mz contract <file>` | evaluates the file's `contract` block (only on a file that checks clean) |\n| `mz contract --agent <file>` | the same, as NDJSON; the summary adds `contract_clauses` and `contract_failures` |\n| `mz outline <file>` | the interface only (name, enums, props, contract subjects), as valid Mzizi |\n| `mz hash <file>` / `mz ir <file>` | the content-addressed root hash / every IR node with its hash and path |\n\nExit status: **0** no errors (warnings do not fail), **1** errors or a failed contract\nassertion, **2** a usage or I/O problem. Branch on the status; parse the NDJSON for detail.\n\n`check` and `contract` are deliberately separate: a compile error is not a defect, and a\ncontract failure is. Your file is done when **both** `mz check --agent` and\n`mz contract --agent` exit 0.\n\n## The loop\n\n1. Write the file.\n2. `mz check --agent file.mz`. If it exits 0, go to 5.\n3. `mz fix file.mz` to apply every `exact` fix. Read each remaining diagnostic's `say`; apply\n `guess` fixes only after checking them.\n4. Fix everything reported, then go to 2. Errors are independent, so fix them all before\n re-running; do not fix one and re-run to \"see the next\".\n5. `mz contract --agent file.mz`. `MZ0603` means an assertion is false (a defect in the\n component or the contract); `MZ0605` means it could not be evaluated, which **counts as a\n failure**.\n\n## Reading the NDJSON\n\nThis file is wrong on purpose. It is the example in `benchmarks/prompts/mzizi-guide.md`, which\n`benchmarks/prompts/verify-guide.sh` checks against the real compiler.\n\n```mz\n## WRONG ON PURPOSE: three mistakes.\ncomponent tag\n\n enum tag_size\n snug class \"h-13 px-3\"\n end\n\n prop onPick: event(none)\n prop size: tag_size = snug\n\n view\n row\n slot = \"tag\"\n class = \"rounded-full {size.class}\"\n tap = on_pick\n end\n end\n\n contract\n slot is \"tag\"\n tag_size.snug height 52\n end\n\nend\n```\n\n`mz check --agent tag.mz` exits 1 and prints exactly:\n\n```text\n{\"code\":\"MZ0101\",\"severity\":\"error\",\"file\":\"tag.mz\",\"span\":[8,8,8,14],\"say\":\"`onPick` is not snake_case — Mzizi names are snake_case, so write `on_pick`\",\"fix\":{\"span\":[8,8,8,14],\"replace\":\"on_pick\",\"confidence\":\"exact\"}}\n{\"code\":\"MZ0602\",\"severity\":\"error\",\"file\":\"tag.mz\",\"span\":[21,26,21,28],\"say\":\"a contract clause needs a predicate (is / in / contains / not_empty / at_least / uses / min_height / shows), found `52`\",\"fix\":{\"span\":[21,26,21,26],\"replace\":\"is \",\"confidence\":\"exact\"}}\n{\"code\":\"MZ0208\",\"severity\":\"error\",\"file\":\"tag.mz\",\"span\":[24,1,24,4],\"say\":\"top-level blocks close with their name: write `end component tag`\",\"fix\":{\"span\":[24,1,24,4],\"replace\":\"end component tag\",\"confidence\":\"exact\"}}\n{\"summary\":true,\"errors\":3,\"warnings\":0,\"exact_fixable\":3,\"ms\":0}\n```\n\n`mz fix tag.mz` then prints `mz: applied 3 exact fixes; 0 errors (0 exact-fixable) remain`, and\n`mz contract --agent tag.mz` reports `\"contract_clauses\":2,\"contract_failures\":0`.\n\n- `code`, `severity` (`error` or `warning`), `file` (the path you passed), `span`\n `[line, col, end_line, end_col]` (1-indexed, end exclusive) and `say` are always present.\n- `say` quotes the offending source, so you need not re-read the file to understand it.\n- `fix` is optional: put `replace` at `fix.span`. An empty `replace` is a deletion. `exact`\n is safe to apply blind; `guess` has the right location and an inferred replacement.\n- The last line is always the summary.\n\n## Syntax essentials\n\nOne file holds one component. There is one form per intent: if a construct is not shown here or\nin the RFCs, assume it does not exist. The order inside a component is doc lines, `use`, `enum`\nand `record`, `prop`, `view`, `fn`, `contract`.\n\nA complete primitive, `primitives/badge.mz`:\n\n```mz\n## A small status label. Not interactive, so no touch floor applies.\n## Corpus reference: n2-primitives/badge.tsx.\ncomponent badge\n\n enum badge_variant\n default class \"bg-primary text-primary-foreground\"\n secondary class \"bg-secondary text-secondary-foreground\"\n destructive class \"bg-destructive/10 text-destructive dark:bg-destructive/20\"\n outline class \"border-border text-foreground bg-input/30\"\n ghost class \"hover:bg-muted hover:text-muted-foreground\"\n link class \"text-primary underline-offset-4 hover:underline\"\n end\n\n prop variant: badge_variant = default\n prop label: text\n\n view\n mark\n slot = \"badge\"\n portal = \"https://mzizi.dev/components/badge\"\n variant = variant\n class = \"inline-flex h-5 w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-md border border-transparent px-2 py-0.5 text-xs font-medium whitespace-nowrap transition-all {variant.class}\"\n text = label\n end\n end\n\n contract\n every badge_variant class not_empty\n badge_variant.default class contains \"bg-primary\"\n slot is \"badge\"\n end\n\nend component badge\n```\n\n- **Shape.** `component <name>` opens the file and `end component <name>` closes it, repeating\n the name. Every other block (`enum`, `record`, `view`, `contract`, each element, each `when`,\n each `for each`, each `fn`) closes with a bare `end`. Never `end enum` or `end view`.\n- **Comments.** `##` to end of line is the only comment form. `//` and `/* */` are errors.\n- **Names** are `snake_case`. `onRemove` is `MZ0101`, with the snake form as its `exact` fix.\n- **Enums are variant tables.** One row per variant: its name, then `column value` pairs. Every\n variant has every column (`MZ0303` otherwise). A column can carry meaning beyond styling, as in\n `primitives/alert.mz`, where `announce` ties the ARIA role to the severity:\n\n ```mz\n enum alert_variant\n default class \"bg-card text-card-foreground\" announce \"status\"\n destructive class \"bg-card text-destructive\" announce \"alert\"\n warning class \"bg-card text-[var(--status-warning,#FFD740)]\" announce \"alert\"\n info class \"bg-card text-[var(--status-info,#00B0FF)]\" announce \"status\"\n end\n ```\n\n- **Heights come from the class.** A variant's `height` is the pixels its `h-N` or `size-N`\n class renders (`N × 4`). You may leave the `height` column out. A written `height` that\n disagrees with the class is `MZ0313`, and its `exact` fix is the rendered number.\n- **Props.** `prop <name>: <type>`, optionally `= <default>`. The types are an enum name\n (`= default`), `text`, `bool`, `int`, `event(none)`, `event(<type>)`, `list(<type>)`\n (omitted means empty), `option(<type>)` (omitted means none) and a record name. Never\n `option(list(T))` (`MZ0703`).\n- **The view** holds exactly one element tree. An element is a bare word on its own line,\n closed by `end`. Inside it, each line is an attribute `name = value` (the `=` is required),\n a child element, a `when` block, a `for each`, or `nothing`. Strings interpolate with\n `{...}`; there is no `+`.\n- **Conditionals.** `when x` … `end`, `when not x`, `when v is <variant>`, and for an option or\n a list `when x is none` … `else` … `end`. There is no `if` (`MZ0407`, `exact` fix `when`)\n and no `match`. From `primitives/avatar.mz`:\n\n ```mz\n when image is none\n row\n slot = \"avatar-fallback\"\n class = \"flex size-full items-center justify-center rounded-full bg-muted text-muted-foreground {size.text_class}\"\n text = initials\n end\n else\n picture\n slot = \"avatar-image\"\n source = image\n alt = alt\n class = \"aspect-square size-full rounded-full object-cover\"\n end\n end\n ```\n\n- **Lists** render with `for each item in items`, whose first line is its `key = item.<field>`.\n- **Capabilities.** `use motion` if the component animates (`primitives/spinner.mz`). Nothing\n else needs `use`.\n- **Events.** Bind with `tap = on_tap` / `change = on_change`. A parameterless `fn` can `emit`\n an event prop, as in `examples/connectivity_bar.mz`:\n\n ```mz\n fn retry\n emit on_state_change(syncing)\n end\n ```\n\n- **No React carry-overs.** `...props` spreads are `MZ0106` (`exact` fix deletes the line).\n An `as_child` prop is warning `MZ0312`: delete it, and keep what the `as_child = false`\n branch renders. `className` pass-through has no equivalent.\n\n`benchmarks/prompts/mzizi-guide.md` is the complete one-file guide the benchmark gives a model,\nincluding records, `for each` and a React-to-Mzizi table. Read it before writing a component.\n\n## Contracts\n\nEvery component carries a `contract … end` block: one assertion per line,\n`<subject> <predicate>`, no colon. A file with no contract is warning `MZ0501`. It is checked\nagainst the file's own enums, view attributes and prop defaults (RFC-0006). From\n`primitives/button.mz`:\n\n```mz\n contract\n every button_size height at_least 48\n button_size.default height is 56\n button_size.sm height is 48\n button_variant.default class contains \"bg-primary\"\n slot is \"button\"\n end\n```\n\n| Subject | Means |\n| --------------------------- | -------------------------------------------------- |\n| `every <enum> <column>` | that column in every variant |\n| `<enum>.<variant> <column>` | one cell |\n| `<attribute>` | the root view element's attribute (`slot`, `role`) |\n| `<prop>` | the prop's default (only a prop that has one) |\n| `<element> \"<text>\"` | the element carrying that text |\n| `when <name>` | the `when` branch that mentions that name |\n\nPredicates: `is <value>` (exactly as written: `height is \"52\"` fails against `height 52`),\n`contains \"<s>\"`, `not_empty`, `at_least <n>`, `in \"<a>\" \"<b>\"`, `uses \"--token\"`,\n`uses <component>` (no subject), `shows <element> \"<t>\"` (after `when <name>`) and\n`min_height <n>`. `examples/connectivity_bar.mz` uses the last two:\n\n```mz\n when offline shows button \"Retry\"\n button \"Retry\" min_height 48\n```\n\nAssert what the source guarantees: every interactive size clears 48px, the `slot`, and the key\ncells. An assertion on a prop with no default is `MZ0605` (unevaluable), which fails.\n\n`mz contract` checks nothing rendered. The rendered half is `benchmarks/harness`, which diffs a\n`.mz` component's variants, defaults, touch heights and `data-slot` set against the Rust\nreference. Mzizi Roots components carry the same clause grammar as `pub const CONTRACT` and\ncheck it against their server-rendered markup (see the `mzizi-roots` skill).\n\nThe direction (RFC-0010, draft) is that **everything built carries a contract**: components,\nlanguage functions and backend handlers. Only the component form is evaluated today.\n\n## Diagnostic codes you will meet\n\nThe ranges are `MZ01xx` lexing and names, `MZ02xx` blocks and closers, `MZ03xx` declarations,\n`MZ04xx` the view, `MZ05xx` lints, `MZ06xx` contracts and `MZ07xx` types and resolution. The\nfull tables are RFC-0001 §4.7, RFC-0006 §8 and RFC-0008 (types).\n\n| Code | Severity | Written | Repair |\n| ------------------- | -------- | ----------------------------------------------------------- | ---------------------------------------------- |\n| `MZ0101` | error | a name that is not `snake_case` | `exact`: the snake form |\n| `MZ0105` | error | `list<T>`, `T[]`, `[T]` | `exact`: `list(T)` |\n| `MZ0106` | error | `...props`, `{...props}`, `..attributes` | `exact`: delete (the whole line when alone) |\n| `MZ0204` | error | a block never closed | `guess`: insert the `end`s |\n| `MZ0206`–`MZ0208` | error | a wrong closer (`end view`, a bare `end` for `component`) | `exact`: rewrite the whole closer line |\n| `MZ0303` | error | a variant missing a column another variant has | add the column |\n| `MZ0312` | warning | `prop as_child: bool` (React `asChild`) | `exact` when unread: delete the prop |\n| `MZ0313` | error | `icon class \"size-14\" height 48` | `exact`: the rendered px (`56`) |\n| `MZ0406` | error | `class \"flex\"` (attribute without `=`) | insert `=` (`exact` for known attribute words) |\n| `MZ0407` | error | `if open` | `exact`: `when open` |\n| `MZ0408` | error | an attribute directly under `view` | move it inside the root element |\n| `MZ0409` | error | `span class = \"x\"` (more after an element word) | put the element word on its own line |\n| `MZ0501` | warning | no `contract` block | write one |\n| `MZ0502` | warning | `<other>_enum.variant` from another component | none until modules |\n| `MZ0602` | error | a clause with no predicate (`height 52`) | `exact`: insert `is` (needs an operand) |\n| `MZ0603` | error | **an assertion that does not hold** | fix the component or the clause |\n| `MZ0605` | error | an assertion that cannot be evaluated | assert something the file defines |\n| `MZ0701` | error | an unknown type (`strng`) | alias or nearest-name fix |\n| `MZ0706` | error | a default that does not fit its type | varies |\n| `MZ0707` / `MZ0708` | error | an unknown name / variant, column or field | nearest-name fix |\n| `MZ0710` | error | an option used without `when … is none … else` | narrow it first |\n| `MZ0711` / `MZ0712` | error | a wrong kind of value / a `when` that does not fit its type | see `say` |\n\nTwo real outputs, from pilot-2 candidates written by the 7B model and re-checked on `main`\n(`benchmarks/results/2026-09-27-pilot-2/raw/scored/…/mzizi/{button,badge}/seed-1/iter-01/candidate.mz`):\n\n```text\n{\"code\":\"MZ0313\",\"severity\":\"error\",\"file\":\"candidate.mz\",\"span\":[16,35,16,37],\"say\":\"`icon` declares `height 48` but its class `size-14` renders 56px (N × 4) — write `height 56`, or drop the column and let the class say it once\",\"fix\":{\"span\":[16,35,16,37],\"replace\":\"56\",\"confidence\":\"exact\"}}\n{\"code\":\"MZ0106\",\"severity\":\"error\",\"file\":\"candidate.mz\",\"span\":[15,8,15,16],\"say\":\"`...props` is a spread, and Mzizi has none: a component names each prop it reads and each attribute it sets. `props` has no equivalent — delete the line\",\"fix\":{\"span\":[15,1,16,1],\"replace\":\"\",\"confidence\":\"exact\"}}\n```\n\n## Contributing to the language\n\n- A language change needs an RFC in `design/`. Take the next unused number (RFC-0005 is\n reserved; RFC-0011 is on a branch).\n- CI runs `cargo fmt -- --check`, `cargo clippy --all-targets -- -D warnings`, `cargo test`,\n and `mz check` and `mz contract` over every file in `examples/` and `primitives/`, one file\n per call. `AGENTS.md` in the repo has the exact script.\n- Pull requests are rebase-merged. State what you measured in the commit message.\n- The repo consumes nothing else in the org: no build step, test or script may reach into\n `mzizi-registry` or `agent-tools`.\n\n## See also\n\n- `CHARTER.md` (v0.3), `design/RFC-0001` (syntax and the agent protocol), `RFC-0006`\n (contracts), `RFC-0008` (types, collections, records), `RFC-0009` (the comparison benchmark\n and the kill criterion), `RFC-0010` (contracts everywhere, draft) and\n `benchmarks/READINESS.md`.\n- The `mzizi-roots` skill: the Rust components the language is benchmarked against, and where a\n lowering would target.",
14
+ "category": "language",
15
+ "description": "Write and check Mzizi language source (.mz): LANGUAGE-TRACKER.md as the one list of what Mzizi can and cannot do, the component syntax (enum tables, props, the view tree, when / for each, records, use motion), the backend service (routes, handlers with when / header / respond, example and ensure contracts; RFC-0011), the mz check --agent loop and its NDJSON diagnostics, mz fix for every exact fix, mz contract (a component's contract, or a service run in process), mz build (a service to a local Rust + axum package), the diagnostic codes you will meet (MZ0101, MZ0106, MZ0312, MZ0313, MZ0406–MZ0409, MZ06xx, MZ07xx, MZ08xx), and what is not built yet: expressions, bindings, callable functions, loops, error handling, modules, a standard library, component lowering, a release.",
16
+ "body_mdx": "# Writing and checking Mzizi\n\n**Mzizi is a general-purpose programming language, built to make Rust better, the way\nTypeScript makes JavaScript better** (`CHARTER.md` v0.4, \"Mzizi: a general-purpose\nprogramming language\"). How: no borrows, lifetimes or ownership in the language you write\n(RFC-0001 §1.8), with the harness at the core, the layer an agent reads. \"Makes Rust better\"\nis the goal Phase 0 measures, not a result. Only a backend `service` lowers to Rust today; no\ncomponent does.\n\nRust is its platform, the way JavaScript is TypeScript's: Mzizi is designed to lower to Rust (RFC-0001 §5), and Mzizi\nRoots is its component model, the way React is JavaScript's. Its goal is to be the language\npeople choose instead of TypeScript, Python and C++: write Mzizi, get Rust underneath. That\nis a goal, not a result. Phase 0 has one goal: to build Mzizi as a programming language,\nmeasured against the best existing language for each kind of task (RFC-0009). Nothing has\nbeen measured against that yet.\nIts syntax, type system and diagnostics are designed for machine authorship.\n\nThe language lives in [`mzizi-dev/mzizi`](https://github.com/mzizi-dev/mzizi) (public). Say\nwhich thing is which when you describe it:\n\n- **The language:** its syntax, type system, semantics, and contracts as a language\n feature.\n- **The harness, the core of Mzizi:** the layer an agent reads and works through. It covers\n the language as an agent sees it (grammar, types, contracts, canonical form), the agent\n protocol (`mz check --agent`, its diagnostics and fixes; RFC-0001 §4), and the plugin\n host that the toolchain, the CLI, the MCP server and plugins attach to natively. Part of\n it exists today, in the toolchain: the `mz check --agent` output this skill teaches. The\n harness as a whole is designed, not built: RFC-0012 (a draft) specifies it, and there is no\n `mz harness` command and no plugin host, so never say it works. It lives in\n `mzizi-dev/mzizi`, the language repository, and the `agent-tools`\n packages (`@nyuchi/mzizi-cli`, `mzizi-mcp`, `fundi`) are its clients. It is not\n `benchmarks/harness/`, which is the Phase 0 benchmark scorer.\n- **The toolchain, which implements and supports the language:** `mz` (the compiler, its\n IR and its NDJSON diagnostics, `mz fix`, `mz contract`, `mz build`), `@nyuchi/mzizi-cli`, the MCP\n server at `mcp.mzizi.dev`, these skills, and the Phase 0 benchmark harness. They are\n meant to attach to the harness. Never call the compiler \"the language\".\n- **The components, which support the language:** Mzizi Roots and the registry\n (`mzizi-dev/mzizi-registry`), the language's UI layer. That is the companion\n `mzizi-roots` skill. Supplying the benchmark's UI tasks is one of their jobs, not what\n they are.\n\nThis skill describes `main` at `62a0f32` (2026-09-30): 425 tests in the workspace\n(`cargo test --workspace`, 294 of them in the compiler crate) and 12,644 lines in\n`compiler/src`. Where this skill and the code disagree, **the code is the fact** (RFC-0003\n§7.1). Every example below is copied from a file in the repository that passes `mz check`\nand `mz contract` there.\n\n## Check the tracker before you claim anything\n\n[`LANGUAGE-TRACKER.md`](https://github.com/mzizi-dev/mzizi/blob/main/LANGUAGE-TRACKER.md) in\nthe language repository is **the one list of what Mzizi can and cannot do** (owner, 2026-09-30).\nIf a capability is not ✅ there, Mzizi does not have it, whatever any other page says, this\nskill included. Its status column is what the code on `main` does, never what an RFC designs.\nRead it before you tell anyone Mzizi can do something, and before you write Mzizi that relies\non a construct this skill does not show.\n\nWhat exists is the front end of a language whose first domains are UI components and one\nbackend slice. What the tracker lists as missing is almost everything a general-purpose\nprogram needs:\n\n- **No expressions or operators** (C1): no arithmetic, no `+`, no `==`. Comparisons exist only\n inside contracts and handler conditions (`is`, `in`, `at_least`, `at_most`).\n- **No bindings** (C2): no variables, no named values, no assignment.\n- **No callable functions** (C3): `fn` parses, but a body is nothing beyond `emit`; there are no\n parameters, return values or calls.\n- **No loops** (C4) outside a view's `for each`, and no early return except a handler's\n `respond`.\n- **No error handling** (C9): no result type and no propagation. Contracts check behaviour;\n they are not error handling.\n- **No modules or imports** (P1): one component or one service per file.\n- **No standard library** (P2), no text operations (C6), `int` is the only number (C5), and\n there is no map, set or tuple (C7).\n- **No program entry point that runs** (C10): there is no `mz run`.\n\nThe public benchmark suites (MultiPL-E, EvalPlus) are blocked until functions exist.\n\n## What exists, and what does not\n\n**Built and tested, in the toolchain:** the lexer, the recovering parser, the name and type\nresolver, the agent diagnostic protocol (`mz check --agent`), `mz fix`, the content-addressed\nIR, `mz outline`, contract evaluation (`mz contract`), nine primitives written in Mzizi\n(`primitives/*.mz`), two component examples and one service (`examples/*.mz`), and the Phase 0\nbenchmark harness and runner. All of it is gated in CI.\n\n**The backend slice (RFC-0011), built and tested:** a `service` with HTTP routes and handlers\n(`when`, `header`, `respond`), checked by `mz check`. `mz contract` runs a service in process\nand evaluates its `example` and `ensure` clauses. `mz build` lowers a service to a local\nRust + axum package, which CI compiles, tests and serves. That is **the whole of the lowering\ntoday:** no component lowers, and the package is a local server, with no Workers, WebAssembly\nor Containers target and no deployment. The `mzizi-be` benchmark arm, the probe crate\n`mzprobe` and backend task B1 exist; no backend episode has run.\n\n**Not built:** everything in the tracker list above, lowering of components, rendering, a\nrelease and a published binary. A component `.mz` file today is checked and its contract\nevaluated; **nothing runs it.** Do not tell anyone Mzizi \"compiles to Rust\" or is \"production\nready\"; say it is designed to lower to Rust, and that only a service does so today.\n\n**In progress, as design only:**\n\n- **The harness.** RFC-0012 (draft). Only the agent protocol exists (`mz check --agent`,\n `mz fix`, `mz contract`).\n- **Contracts everywhere.** RFC-0010 (draft) extends contracts to functions, handlers,\n services and records. Component contracts (RFC-0006) and service contracts (RFC-0011 §7) are\n evaluated. Of RFC-0010's codes, `MZ0606`, `MZ0611`, `MZ0612` and `MZ0613` exist, for\n services; `MZ0607`–`MZ0610` do not exist in the compiler.\n- **Request bodies** for a service (`body input: <record>` and `reject` blocks, RFC-0011 §4.3)\n are designed and not built. So is state (task B4).\n- **Modules.** There are no imports. Components are referenced by bare name\n (`primitives/confirm_bar.mz` uses `button` and `alert` with no import line). A reference to\n another component's enum (`button_variant.ghost`) is warning `MZ0502`, because it cannot be\n checked until modules exist.\n\n**Results, as they fell.** Two Phase 0 pilots ran on 2026-09-27 and neither showed an\nadvantage for Mzizi. On the frontier model the Mzizi and Dioxus arms tied, with Mzizi about 8%\nfewer transcript tokens. On the ~7B open-weight model, the design target, Mzizi did worse on\nall three metrics. Neither pilot is the kill-criterion run (RFC-0009 §6), and that run has not\nhappened. The pilots were tests inside Phase 0 on a few registry UI components against\nDioxus, not its goal. \"Designed for agents\" and \"aims to replace TypeScript, Python and C++\"\nare accurate; \"faster\" or \"better\" is not.\n\n## Getting `mz`\n\nThere is no release. Build it from source; the compiler crate has zero dependencies:\n\n```bash\ngit clone https://github.com/mzizi-dev/mzizi.git && cd mzizi/compiler\ncargo build --bin mz # ../target/debug/mz\ncargo run --bin mz -- check ../primitives/button.mz\n```\n\n**Never `cargo install mz`.** The `mz` crate on crates.io is an unrelated project, and the\ncompiler crate (`mzizi-lang-compiler`) is `publish = false`.\n\n## The commands\n\n`mz` takes exactly one file per call. `mz check ../primitives/*.mz` is a usage error (exit 2).\nLoop over files instead.\n\n| Command | Does |\n| ----------------------------------- | -------------------------------------------------------------------------------- |\n| `mz check <file>` | human-readable diagnostics |\n| `mz check --agent <file>` | NDJSON: one diagnostic per line, then one summary line. **Target this.** |\n| `mz fix <file>` | applies every `exact` fix in place, in one pass, then re-checks the file |\n| `mz contract <file>` | evaluates the file's `contract` block (only on a file that checks clean) |\n| `mz contract --agent <file>` | the same, as NDJSON; the summary adds `contract_clauses` and `contract_failures` |\n| `mz outline <file>` | the interface only (name, enums, props, contract subjects), as valid Mzizi |\n| `mz hash <file>` / `mz ir <file>` | the content-addressed root hash / every IR node with its hash and path |\n| `mz build <service.mz> --out <dir>` | lowers a service to a Rust + axum Cargo package in `<dir>` (only a clean file) |\n\nExit status: **0** no errors (warnings do not fail), **1** errors or a failed contract\nassertion, **2** a usage or I/O problem. Branch on the status; parse the NDJSON for detail.\n\nOn a **service**, `mz contract` runs the service in process (no lowering, no socket), and the\nsummary adds `contract_tested`, the number of generated requests its `ensure` clauses were\ntested over. `mz outline`, `mz hash` and `mz ir` do not cover services yet and exit 2. `mz\nbuild` on a component exits 2 (\"components do not lower yet\"), and on a file with errors it\nprints them, builds nothing and exits 1.\n\n`check` and `contract` are deliberately separate: a compile error is not a defect, and a\ncontract failure is. Your file is done when **both** `mz check --agent` and\n`mz contract --agent` exit 0.\n\n## The loop\n\n1. Write the file.\n2. `mz check --agent file.mz`. If it exits 0, go to 5.\n3. `mz fix file.mz` to apply every `exact` fix. Read each remaining diagnostic's `say`; apply\n `guess` fixes only after checking them.\n4. Fix everything reported, then go to 2. Errors are independent, so fix them all before\n re-running; do not fix one and re-run to \"see the next\".\n5. `mz contract --agent file.mz`. `MZ0603` means an assertion is false (a defect in the\n component or the contract); `MZ0605` means it could not be evaluated, which **counts as a\n failure**.\n\n## Reading the NDJSON\n\nThis file is wrong on purpose. It is the example in `benchmarks/prompts/mzizi-guide.md`, which\n`benchmarks/prompts/verify-guide.sh` checks against the real compiler.\n\n```mz\n## WRONG ON PURPOSE: three mistakes.\ncomponent tag\n\n enum tag_size\n snug class \"h-13 px-3\"\n end\n\n prop onPick: event(none)\n prop size: tag_size = snug\n\n view\n row\n slot = \"tag\"\n class = \"rounded-full {size.class}\"\n tap = on_pick\n end\n end\n\n contract\n slot is \"tag\"\n tag_size.snug height 52\n end\n\nend\n```\n\n`mz check --agent tag.mz` exits 1 and prints exactly:\n\n```text\n{\"code\":\"MZ0101\",\"severity\":\"error\",\"file\":\"tag.mz\",\"span\":[8,8,8,14],\"say\":\"`onPick` is not snake_case — Mzizi names are snake_case, so write `on_pick`\",\"fix\":{\"span\":[8,8,8,14],\"replace\":\"on_pick\",\"confidence\":\"exact\"}}\n{\"code\":\"MZ0602\",\"severity\":\"error\",\"file\":\"tag.mz\",\"span\":[21,26,21,28],\"say\":\"a contract clause needs a predicate (is / in / contains / not_empty / at_least / uses / min_height / shows), found `52`\",\"fix\":{\"span\":[21,26,21,26],\"replace\":\"is \",\"confidence\":\"exact\"}}\n{\"code\":\"MZ0208\",\"severity\":\"error\",\"file\":\"tag.mz\",\"span\":[24,1,24,4],\"say\":\"top-level blocks close with their name: write `end component tag`\",\"fix\":{\"span\":[24,1,24,4],\"replace\":\"end component tag\",\"confidence\":\"exact\"}}\n{\"summary\":true,\"errors\":3,\"warnings\":0,\"exact_fixable\":3,\"ms\":0}\n```\n\n`mz fix tag.mz` then prints `mz: applied 3 exact fixes; 0 errors (0 exact-fixable) remain`, and\n`mz contract --agent tag.mz` reports `\"contract_clauses\":2,\"contract_failures\":0`.\n\n- `code`, `severity` (`error` or `warning`), `file` (the path you passed), `span`\n `[line, col, end_line, end_col]` (1-indexed, end exclusive) and `say` are always present.\n- `say` quotes the offending source, so you need not re-read the file to understand it.\n- `fix` is optional: put `replace` at `fix.span`. An empty `replace` is a deletion. `exact`\n is safe to apply blind; `guess` has the right location and an inferred replacement.\n- The last line is always the summary.\n\n## Syntax essentials\n\nOne file holds one component, or one service (see \"Services\" below). There is one form per\nintent: if a construct is not shown here or in the RFCs, assume it does not exist. The order\ninside a component is doc lines, `use`, `enum` and `record`, `prop`, `view`, `fn`, `contract`.\n\nA complete primitive, `primitives/badge.mz`:\n\n```mz\n## A small status label. Not interactive, so no touch floor applies.\n## Corpus reference: n2-primitives/badge.tsx.\ncomponent badge\n\n enum badge_variant\n default class \"bg-primary text-primary-foreground\"\n secondary class \"bg-secondary text-secondary-foreground\"\n destructive class \"bg-destructive/10 text-destructive dark:bg-destructive/20\"\n outline class \"border-border text-foreground bg-input/30\"\n ghost class \"hover:bg-muted hover:text-muted-foreground\"\n link class \"text-primary underline-offset-4 hover:underline\"\n end\n\n prop variant: badge_variant = default\n prop label: text\n\n view\n mark\n slot = \"badge\"\n portal = \"https://mzizi.dev/components/badge\"\n variant = variant\n class = \"inline-flex h-5 w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-md border border-transparent px-2 py-0.5 text-xs font-medium whitespace-nowrap transition-all {variant.class}\"\n text = label\n end\n end\n\n contract\n every badge_variant class not_empty\n badge_variant.default class contains \"bg-primary\"\n slot is \"badge\"\n end\n\nend component badge\n```\n\n- **Shape.** `component <name>` opens the file and `end component <name>` closes it, repeating\n the name. Every other block (`enum`, `record`, `view`, `contract`, each element, each `when`,\n each `for each`, each `fn`) closes with a bare `end`. Never `end enum` or `end view`.\n- **Comments.** `##` to end of line is the only comment form. `//` and `/* */` are errors.\n- **Names** are `snake_case`. `onRemove` is `MZ0101`, with the snake form as its `exact` fix.\n- **Enums are variant tables.** One row per variant: its name, then `column value` pairs. Every\n variant has every column (`MZ0303` otherwise). A column can carry meaning beyond styling, as in\n `primitives/alert.mz`, where `announce` ties the ARIA role to the severity:\n\n ```mz\n enum alert_variant\n default class \"bg-card text-card-foreground\" announce \"status\"\n destructive class \"bg-card text-destructive\" announce \"alert\"\n warning class \"bg-card text-[var(--status-warning,#FFD740)]\" announce \"alert\"\n info class \"bg-card text-[var(--status-info,#00B0FF)]\" announce \"status\"\n end\n ```\n\n- **Heights come from the class.** A variant's `height` is the pixels its `h-N` or `size-N`\n class renders (`N × 4`). You may leave the `height` column out. A written `height` that\n disagrees with the class is `MZ0313`, and its `exact` fix is the rendered number.\n- **Props.** `prop <name>: <type>`, optionally `= <default>`. The types are an enum name\n (`= default`), `text`, `bool`, `int`, `event(none)`, `event(<type>)`, `list(<type>)`\n (omitted means empty), `option(<type>)` (omitted means none) and a record name. Never\n `option(list(T))` (`MZ0703`).\n- **The view** holds exactly one element tree. An element is a bare word on its own line,\n closed by `end`. Inside it, each line is an attribute `name = value` (the `=` is required),\n a child element, a `when` block, a `for each`, or `nothing`. Strings interpolate with\n `{...}`; there is no `+`.\n- **Conditionals.** `when x` … `end`, `when not x`, `when v is <variant>`, and for an option or\n a list `when x is none` … `else` … `end`. There is no `if` (`MZ0407`, `exact` fix `when`)\n and no `match`. From `primitives/avatar.mz`:\n\n ```mz\n when image is none\n row\n slot = \"avatar-fallback\"\n class = \"flex size-full items-center justify-center rounded-full bg-muted text-muted-foreground {size.text_class}\"\n text = initials\n end\n else\n picture\n slot = \"avatar-image\"\n source = image\n alt = alt\n class = \"aspect-square size-full rounded-full object-cover\"\n end\n end\n ```\n\n- **Lists** render with `for each item in items`, whose first line is its `key = item.<field>`.\n- **Capabilities.** `use motion` if the component animates (`primitives/spinner.mz`). Nothing\n else needs `use`.\n- **Events.** Bind with `tap = on_tap` / `change = on_change`. A parameterless `fn` can `emit`\n an event prop, as in `examples/connectivity_bar.mz`:\n\n ```mz\n fn retry\n emit on_state_change(syncing)\n end\n ```\n\n- **No React carry-overs.** `...props` spreads are `MZ0106` (`exact` fix deletes the line).\n An `as_child` prop is warning `MZ0312`: delete it, and keep what the `as_child = false`\n branch renders. `className` pass-through has no equivalent.\n\n`benchmarks/prompts/mzizi-guide.md` is the complete one-file guide the benchmark gives a model,\nincluding records, `for each` and a React-to-Mzizi table. Read it before writing a component.\n\n## Services (the backend slice, RFC-0011)\n\nA backend program is one file holding one `service`, closed by `end service <name>`. Every\ninner block (`record`, `route`, `fallback`, `when`, `contract`) closes with a bare `end`. The\norder is doc lines, service-level `header` lines, `record`s (and `enum`s), `route`s,\n`fallback`, `contract`. There are no imports, no `main`, no server setup and no port.\n\n`examples/registry.mz`, the example service (22 clauses, all holding; its `ensure` clauses\ntested over 61 generated requests):\n\n```mz\n## A registry of two components: routes, methods, redirects and a JSON 404.\n## RFC-0011's worked example, shaped like the API gateway's `/v1/ui` routes (RFC-0009 §2.4, B1).\nservice registry\n\n header \"x-mzizi-source\" \"fixture\"\n\n record problem\n field error: text\n end\n\n record health\n field status: text\n end\n\n record item\n field name: text\n end\n\n route health_check\n get \"/v1/health\"\n respond 200 json health status \"ok\"\n end\n\n route ui_list\n get \"/v1/ui\"\n respond 200 file \"fixtures/ui.json\"\n end\n\n route ui_item\n get \"/v1/ui/{name}\"\n when name in \"badge\" \"button\"\n respond 200 json item name name\n end\n header \"cache-control\" \"private, no-cache, no-store, max-age=0, must-revalidate\"\n respond 404 json problem error \"Not found\"\n end\n\n fallback\n header \"cache-control\" \"private, no-cache, no-store, max-age=0, must-revalidate\"\n respond 404 json problem error \"Not found\"\n end\n\n contract\n example get \"/v1/health\" body.status is \"ok\"\n example get \"/v1/ui\" status is 200\n example get \"/v1/ui\" header \"content-type\" is \"application/json\"\n example get \"/v1/ui/button\" status is 200\n example get \"/v1/ui/button\" body.name is \"button\"\n example get \"/v1/ui/does-not-exist\" status is 404\n example get \"/v1/ui/does-not-exist\" body.error is \"Not found\"\n example get \"/v1/ui/does-not-exist\" header \"cache-control\" is \"private, no-cache, no-store, max-age=0, must-revalidate\"\n example head \"/v1/ui/button\" status is 200\n example head \"/v1/ui/button\" body is \"\"\n example options \"/v1/ui\" status is 204\n example options \"/v1/ui\" header \"allow\" is \"GET, HEAD, OPTIONS\"\n example delete \"/v1/ui/button\" status is 405\n example delete \"/v1/ui/button\" body is \"\"\n example get \"/v1/ui/\" status is 308\n example get \"/v1/ui/?limit=2\" header \"location\" is \"/v1/ui?limit=2\"\n example get \"/nope\" status is 404\n example get \"/nope\" header \"content-type\" is \"application/json\"\n ensure status in 200 204 308 404 405\n ensure header \"x-mzizi-source\" is \"fixture\"\n ensure when status is 404 then body.error is \"Not found\"\n ensure when status is 405 then header \"allow\" is \"GET, HEAD, OPTIONS\"\n end\n\nend service registry\n```\n\n- **A route** is `route <name>`, then exactly one `<method> \"<pattern>\"` line, then zero or\n more `query <name>: option(<scalar>)` lines, then statements, then `end`. The methods are\n `get`, `head`, `post`, `put`, `patch`, `delete` and `options`, lower case; a second method on\n the same path is a second route.\n- **A pattern** is `/` or `/`-separated segments of lower-case letters, digits and `-._~`,\n or a whole-segment `{name}`, which binds `name` as `text`. No trailing slash, no empty\n segment, no `?`. A literal segment beats a parameter at the same position.\n- **A query parameter is always an option:** `query limit: option(int)`. `option(int)` is\n `-`? then ASCII digits, so `\"0x10\"`, `\"1e2\"`, `\" 7 \"` and `\"1.5\"` are `none`; `option(bool)`\n is exactly `true` or `false`. Bad input is absence, never an error.\n- **A handler has three statements**, and every path through it must end in `respond`:\n\n | Statement | Means |\n | -------------------------------- | --------------------------------------------------------- |\n | `when <cond>` … [`else` …] `end` | a branch; with no `else`, a false condition falls through |\n | `header \"<name>\" \"<value>\"` | set a header on the response this path sends |\n | `respond <status> [<body>]` | send the response; nothing after it on the path runs |\n\n A condition is `p` / `not p` (a `bool`), `p is <literal>`, `p in <literal> …`,\n `p at_least <n>` / `p at_most <n>` (an `int`), or `p is none` … `else` (an option, which is\n narrowed in the `else`). There is no `if`, `==`, `return`, assignment, loop or call.\n\n- **Bodies:** none; `json <record> <field> <value> …` (every field once, a literal or a\n parameter, no punctuation; `application/json`); `text \"<string>\"` (may interpolate a\n parameter as `{name}`); or `file \"<path>\"` (a fixture beside the `.mz` file, embedded at\n build time). A response record's fields are scalars or options of scalars. `204`, `304` and\n `1xx` take no body.\n- **Headers** are lower case. A service-level `header` applies to every response, runtime\n ones included; a route's own `header` for the same name wins. `content-length` and\n `transfer-encoding` belong to the server.\n- **The runtime answers what HTTP defines, so do not write it:** `HEAD` for a `get` route\n (its status and headers, no body); `OPTIONS` (`204` with `allow`, e.g. `GET, HEAD, OPTIONS`);\n any other undeclared method on a matched path (`405` with the same `allow`, no body); and a\n trailing slash or `//` (`308` to the canonical path, query kept, never off-site). A path no\n route matches runs `fallback`, or gets an empty `404` when there is none.\n- **The contract:** `example <method> \"<target>\" <facet> <predicate>` sends one request;\n `ensure [when <facet> <predicate> then] <facet> <predicate>` must hold for every generated\n request. A facet is `status`, `header \"<name>\"`, `body` or `body.<field>`; a predicate is\n `is <value>`, `in <value> …`, `not_empty`, `contains \"<text>\"`, `at_least <n>` or\n `at_most <n>`. `header \"<name>\" is none` asserts absence and `body is \"\"` an empty body.\n `ensure` is **tested** over the generated set, never proven.\n\nThis file is wrong on purpose. It is the example in `benchmarks/prompts/mzizi-be-guide.md`:\n\n```mz\n## WRONG ON PURPOSE: three mistakes.\nservice shop\n\n record problem\n field error: text\n end\n\n route item\n GET \"/v1/items/{id}/\"\n when id is \"1\"\n respond 200 text \"one\"\n end\n end\n\nend service shop\n```\n\n`mz check --agent candidate.mz` exits 1 and prints:\n\n```text\n{\"code\":\"MZ0613\",\"severity\":\"warning\",\"file\":\"candidate.mz\",\"span\":[2,9,2,13],\"say\":\"`service shop` has no `contract` block — its behaviour is unverified (RFC-0010 §8)\"}\n{\"code\":\"MZ0801\",\"severity\":\"error\",\"file\":\"candidate.mz\",\"span\":[9,5,9,8],\"say\":\"`GET` is not a method — a route declares one of get, head, post, put, patch, delete, options\",\"fix\":{\"span\":[9,5,9,8],\"replace\":\"get\",\"confidence\":\"exact\"}}\n{\"code\":\"MZ0802\",\"severity\":\"error\",\"file\":\"candidate.mz\",\"span\":[9,9,9,26],\"say\":\"`\\\"/v1/items/{id}/\\\"` ends with `/` — patterns have no trailing slash; the runtime redirects `…/` to the path without it\",\"fix\":{\"span\":[9,9,9,26],\"replace\":\"\\\"/v1/items/{id}\\\"\",\"confidence\":\"exact\"}}\n{\"code\":\"MZ0804\",\"severity\":\"error\",\"file\":\"candidate.mz\",\"span\":[13,3,13,6],\"say\":\"a path through `route item` reaches `end` without a `respond` — when the `when` on line 10 does not respond on both branches; every path must respond\"}\n{\"summary\":true,\"errors\":3,\"warnings\":1,\"exact_fixable\":2,\"ms\":0}\n```\n\n`mz fix` applies the two `exact` fixes. `MZ0804` needs a decision: add\n`respond 404 json problem error \"Not found\"` after the `when`'s `end`, so the false path\nresponds too.\n\n**Building it.** `mz build registry.mz --out <dir>` checks the file and, with zero errors,\nwrites `Cargo.toml`, `src/main.rs` and the fixtures. The package is its own workspace root,\ngenerated output that is never committed, and depends on `axum =0.8.9` and `tokio =1.53.1`\n(tests add `tower`, `http-body-util` and `serde_json`). Every `example` becomes one\n`#[tokio::test]` (`cargo test` in the package); `ensure` clauses are evaluated by\n`mz contract` only. `cargo run --release --manifest-path <dir>/Cargo.toml` serves\n`127.0.0.1:$PORT` (8080 by default) and prints `listening on http://127.0.0.1:<port>`.\n\n## Contracts\n\nEvery component carries a `contract … end` block: one assertion per line,\n`<subject> <predicate>`, no colon. A file with no contract is warning `MZ0501`. It is checked\nagainst the file's own enums, view attributes and prop defaults (RFC-0006). From\n`primitives/button.mz`:\n\n```mz\n contract\n every button_size height at_least 48\n button_size.default height is 56\n button_size.sm height is 48\n button_variant.default class contains \"bg-primary\"\n slot is \"button\"\n end\n```\n\n| Subject | Means |\n| --------------------------- | -------------------------------------------------- |\n| `every <enum> <column>` | that column in every variant |\n| `<enum>.<variant> <column>` | one cell |\n| `<attribute>` | the root view element's attribute (`slot`, `role`) |\n| `<prop>` | the prop's default (only a prop that has one) |\n| `<element> \"<text>\"` | the element carrying that text |\n| `when <name>` | the `when` branch that mentions that name |\n\nPredicates: `is <value>` (exactly as written: `height is \"52\"` fails against `height 52`),\n`contains \"<s>\"`, `not_empty`, `at_least <n>`, `in \"<a>\" \"<b>\"`, `uses \"--token\"`,\n`uses <component>` (no subject), `shows <element> \"<t>\"` (after `when <name>`) and\n`min_height <n>`. `examples/connectivity_bar.mz` uses the last two:\n\n```mz\n when offline shows button \"Retry\"\n button \"Retry\" min_height 48\n```\n\nAssert what the source guarantees: every interactive size clears 48px, the `slot`, and the key\ncells. An assertion on a prop with no default is `MZ0605` (unevaluable), which fails.\n\n`mz contract` checks nothing rendered. The rendered half is `benchmarks/harness`, which diffs a\n`.mz` component's variants, defaults, touch heights and `data-slot` set against the Rust\nreference. Mzizi Roots components carry the same clause grammar as `pub const CONTRACT` and\ncheck it against their server-rendered markup (see the `mzizi-roots` skill).\n\nThe direction (RFC-0010, draft) is that **everything built carries a contract**: components,\nlanguage functions and backend handlers. The component form and the service form (\"Services\"\nabove) are evaluated today; functions have no contracts because they have no bodies.\n\n## Diagnostic codes you will meet\n\nThe ranges are `MZ01xx` lexing and names, `MZ02xx` blocks and closers, `MZ03xx` declarations,\n`MZ04xx` the view, `MZ05xx` lints, `MZ06xx` contracts, `MZ07xx` types and resolution, and\n`MZ08xx` services and handlers. The full tables are RFC-0001 §4.7, RFC-0006 §8, RFC-0008\n(types), RFC-0010 §6 and RFC-0011 §9. A service reuses `MZ0701`, `MZ0707`, `MZ0708` and\n`MZ0710` for unknown names and un-narrowed options.\n\n| Code | Severity | Written | Repair |\n| ------------------- | -------- | ---------------------------------------------------------------------------------- | ---------------------------------------------- |\n| `MZ0101` | error | a name that is not `snake_case` | `exact`: the snake form |\n| `MZ0105` | error | `list<T>`, `T[]`, `[T]` | `exact`: `list(T)` |\n| `MZ0106` | error | `...props`, `{...props}`, `..attributes` | `exact`: delete (the whole line when alone) |\n| `MZ0204` | error | a block never closed | `guess`: insert the `end`s |\n| `MZ0206`–`MZ0208` | error | a wrong closer (`end view`, a bare `end` for `component`) | `exact`: rewrite the whole closer line |\n| `MZ0303` | error | a variant missing a column another variant has | add the column |\n| `MZ0312` | warning | `prop as_child: bool` (React `asChild`) | `exact` when unread: delete the prop |\n| `MZ0313` | error | `icon class \"size-14\" height 48` | `exact`: the rendered px (`56`) |\n| `MZ0406` | error | `class \"flex\"` (attribute without `=`) | insert `=` (`exact` for known attribute words) |\n| `MZ0407` | error | `if open` | `exact`: `when open` |\n| `MZ0408` | error | an attribute directly under `view` | move it inside the root element |\n| `MZ0409` | error | `span class = \"x\"` (more after an element word) | put the element word on its own line |\n| `MZ0501` | warning | no `contract` block | write one |\n| `MZ0502` | warning | `<other>_enum.variant` from another component | none until modules |\n| `MZ0602` | error | a clause with no predicate (`height 52`) | `exact`: insert `is` (needs an operand) |\n| `MZ0603` | error | **an assertion that does not hold** | fix the component or the clause |\n| `MZ0605` | error | an assertion that cannot be evaluated | assert something the file defines |\n| `MZ0606` | error | an `example` status that an `ensure status in …` rules out | fix one of the two |\n| `MZ0611` / `MZ0612` | error | a service `ensure` / `example` that does not hold | `say` quotes the request that broke it |\n| `MZ0613` | warning | a service with no or an empty `contract` block | write one |\n| `MZ0701` | error | an unknown type (`strng`) | alias or nearest-name fix |\n| `MZ0706` | error | a default that does not fit its type | varies |\n| `MZ0707` / `MZ0708` | error | an unknown name / variant, column or field | nearest-name fix |\n| `MZ0710` | error | an option used without `when … is none … else` | narrow it first |\n| `MZ0711` / `MZ0712` | error | a wrong kind of value / a `when` that does not fit its type | see `say` |\n| `MZ0801` | error | a route not opening with `<method> \"<pattern>\"`, `GET`, a second method line | `exact` for `GET`; else a `guess` |\n| `MZ0802` | error | a bad pattern: trailing `/`, empty segment, `?`, no leading `/` | `exact` where the repair is certain |\n| `MZ0803` | error | two routes with one method and the same pattern | delete or change one |\n| `MZ0804` | error | a handler path that reaches `end` without `respond` | add the `respond` |\n| `MZ0805` | error | a line after `respond` that can never run | `exact`: delete it |\n| `MZ0806` | error | a bad `respond`: no status, not 100–599, a body on `204` | `exact` deletes a body that is not allowed |\n| `MZ0807` | error | a bad `header`: `\"Cache-Control\"`, set twice, server-owned | `exact`: the lower-case name |\n| `MZ0808` | error | a `json` literal with a missing, repeated or ill-typed field (unknown is `MZ0708`) | give every field once |\n| `MZ0809` | error | `query limit: int` | `exact`: `option(int)` |\n| `MZ0810` | error | a `file \"…\"` fixture that does not exist | add the file or fix the path |\n| `MZ0811` | error | a line a service or handler cannot hold (`return`, `=`, `view`) | see `say` (`is` for `=` is `exact`) |\n| `MZ0812` | error | a second `fallback`, or a route or parameter name used twice | rename or delete one |\n\nTwo real outputs, from pilot-2 candidates written by the 7B model and re-checked on `main`\n(`benchmarks/results/2026-09-27-pilot-2/raw/scored/…/mzizi/{button,badge}/seed-1/iter-01/candidate.mz`):\n\n```text\n{\"code\":\"MZ0313\",\"severity\":\"error\",\"file\":\"candidate.mz\",\"span\":[16,35,16,37],\"say\":\"`icon` declares `height 48` but its class `size-14` renders 56px (N × 4) — write `height 56`, or drop the column and let the class say it once\",\"fix\":{\"span\":[16,35,16,37],\"replace\":\"56\",\"confidence\":\"exact\"}}\n{\"code\":\"MZ0106\",\"severity\":\"error\",\"file\":\"candidate.mz\",\"span\":[15,8,15,16],\"say\":\"`...props` is a spread, and Mzizi has none: a component names each prop it reads and each attribute it sets. `props` has no equivalent — delete the line\",\"fix\":{\"span\":[15,1,16,1],\"replace\":\"\",\"confidence\":\"exact\"}}\n```\n\n## Contributing to the language\n\n- A language change needs an RFC in `design/`. Take the next unused number: RFC-0005 is\n reserved, and RFC-0011 (handlers) and RFC-0012 (the harness) exist, so the next is\n RFC-0013.\n- A PR that changes what the language can do updates its row in `LANGUAGE-TRACKER.md` in the\n same PR, with evidence, and adds a `CHANGELOG.md` entry.\n- CI runs `cargo fmt -- --check`, `cargo clippy --all-targets -- -D warnings`, `cargo test`,\n and `mz check` and `mz contract` over every file in `examples/` and `primitives/`, one file\n per call. Its `lowering` job runs `mz build` on `examples/registry.mz`, then `cargo test` and\n one request over a socket against the package, and `mzprobe verify` of task B1. `AGENTS.md`\n in the repo has the exact script.\n- Pull requests are rebase-merged. State what you measured in the commit message.\n- The repo consumes nothing else in the org: no build step, test or script may reach into\n `mzizi-registry` or `agent-tools`.\n\n## See also\n\n- `LANGUAGE-TRACKER.md` (what Mzizi can and cannot do), `CHARTER.md` (v0.4),\n `design/RFC-0001` (syntax and the agent protocol), `RFC-0006` (contracts), `RFC-0008`\n (types, collections, records), `RFC-0009` (the comparison benchmark and the kill\n criterion), `RFC-0010` (contracts everywhere, draft), `RFC-0011` (handlers, the backend\n slice), `RFC-0012` (the harness, draft), `benchmarks/prompts/mzizi-be-guide.md` (the\n one-file service guide) and `benchmarks/READINESS.md`.\n- The `mzizi-backend` skill: where a Mzizi service stands against the live TypeScript Workers.\n- The `mzizi-roots` skill: the Rust components built to support the language, its component\n model and where a component lowering would target.",
14
17
  "source": "mzizi-dev/agent-tools/mzizi-skills/skills/mzizi-language"
15
18
  },
16
19
  {
17
20
  "name": "mzizi-roots",
18
- "description": "Everything about Mzizi's components: Mzizi Roots, the Rust build (Dioxus UI and host-agnostic server components). cargo add mzizi-roots / mzizi-roots-server, what mzizi_get_component returns (Rust first: lead, rust, react), the node-to-crate map, pub const CONTRACT checked on dioxus-ssr markup, the two frontend paths (Astro with Roots, or pure Rust) and app setup, reuse before build, and adding a component to the registry (the node, the .rs and .tsx, registry.json, per-node checklists, the 48px floor, pnpm check and the cargo gates). The React/TSX build is the deprioritised secondary. Replaces scaffold-component, simplify and half of ecosystem-app-setup.",
19
- "body_mdx": "# Mzizi Roots — the Rust build of Mzizi's components\n\n**Mzizi Roots** is Mzizi's own components in Rust: UI components in Dioxus 0.7, and server\ncomponents as host-agnostic Rust. They live in\n[`mzizi-dev/mzizi-registry`](https://github.com/mzizi-dev/mzizi-registry) and are compiled by\nthe crates in its `mzizi-rs/` workspace. The plan is `docs/roots/RFC-roots.md` there.\n\n**Rust first.** Where a Rust implementation exists, present it and use it first. The React/TSX\ncomponents are **the React build**: they keep working for existing React apps, and they are\ndeprioritised, so no new work goes into them.\n\nThis skill describes registry `main` at `6663cb5` (2026-09-29), the crates published to\ncrates.io at **0.1.0** on 2026-09-29, and `mzizi-mcp` 0.11.0. It replaces three 0.7.0 skills:\n`scaffold-component` (now \"Contributing a component\"), `simplify` (now \"Reuse before build\")\nand the frontend half of `ecosystem-app-setup` (now \"Starting an app\"). Tokens, brand and the\nUbuntu constraints are in the `mzizi-design` skill.\n\n## Install\n\nTwo umbrella crates with no code of their own re-export the node crates behind features:\n\n```sh\ncargo add mzizi-roots # UI: tokens + ui + brand + shell (all default features)\ncargo add mzizi-roots-server # server: assurance + fundi + docs + discovery\n```\n\nTake only what you use:\n\n```toml\n[dependencies]\nmzizi-roots = { version = \"0.1\", default-features = false, features = [\"brand\"] }\n```\n\nTo follow `main`, or pin a commit, install from git. Cargo finds a crate by name anywhere in the\nrepository's workspace:\n\n```sh\ncargo add mzizi-roots --git https://github.com/mzizi-dev/mzizi-registry\n```\n\n```toml\nmzizi-brand = { git = \"https://github.com/mzizi-dev/mzizi-registry\", rev = \"<commit>\" }\n```\n\nA single node crate works too (`cargo add mzizi-ui`). The crates depend on `dioxus` with\n`macro`, `signals`, `hooks` and `html` only, so the app that mounts them chooses web, desktop,\nmobile or server rendering.\n\n`mzizi add <name> --target rust` (`@nyuchi/mzizi-cli`) records the crate and prints the\n`cargo add` rather than copying a `.rs` file into your project: a copied component is a fork\nof a crate module.\n\n## What the MCP returns\n\n`mzizi_get_component` on `https://mcp.mzizi.dev/mcp` (free, no account; `mzizi-mcp` 0.11.0) is\nRust first. It returns `{name, renamedFrom?, lead, note, rust, react, docs?, versions?}`:\n\n- `lead` is `\"rust\"` when the component has a Roots implementation, else `\"react\"`; `note`\n says which build to use.\n- `rust` is the registry's `/v1/rs/<name>` answer with the install first: `crate`\n (`{name, registry, git}`), `install.cargo` (`cargo add <crate>`), `install.pinned`\n (`cargo add <crate> --git https://github.com/mzizi-dev/mzizi-registry --rev <pin>`, which\n builds exactly the source shown), `module` (e.g. `mzizi_brand`), `contract` (the\n `contract … end` text, or `null`), and `files[]` with the `.rs` source inline. With no Rust\n it is `null`.\n- `react` is the React build: `build: \"react\"`, `status: \"deprioritised\"`, `install` (the\n `npx shadcn@latest add https://api.mzizi.dev/v1/ui/<name>` line), then the `/v1/ui/<name>`\n body. It is no longer under a `component` key (that was 0.10.x).\n\n`mzizi_list_components` rows carry `rustCrate` when a component has Rust, the response carries\n`withRust`, and `rust: true` lists only those. `mzizi_search` hits carry `rustCrate` too. For\n`mzizi-hero-stat` the answer is `lead: \"rust\"`, crate `mzizi-brand`, and the four-clause\ncontract shown under \"Contracts\" below.\n\n## The crates\n\nOne crate per registry node that has Rust. The map is `mzizi-rs/crate-for-node.json`, and\n`pnpm rust:generate` refuses a node with Rust in it that no crate claims.\n\n| Node directory | Crate | Umbrella | What it holds |\n| ------------------- | ----------------- | ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `n1-tokens` | `mzizi-tokens` | `mzizi-roots` (always on) | the 21-family palette and scales as Rust consts, generated from `lib/tokens/palette.source.ts` |\n| `n2-primitives` | `mzizi-ui` | `mzizi-roots` (`ui`) | avatar, badge, button, card, chart, input, label, progress, separator |\n| `n3-brand` | `mzizi-brand` | `mzizi-roots` (`brand`) | alert banner, avatar stack, cover header, empty state, escalation card, gauge card, hero stat, meta tile, stats row, success screen, suitability card, user card (all `mzizi-*`) |\n| `n7-shell` | `mzizi-shell` | `mzizi-roots` (`shell`) | bottom nav, command palette, connectivity bar, deep-link handler, footer, mini-app runtime, notification center, persistent player, route guard, theme provider, toast provider, update prompt |\n| `n8-assurance` | `mzizi-assurance` | `mzizi-roots-server` (`assurance`) | probes, telemetry, the OTLP exporter, a11y and RTL conformity checks, alert and incident engines |\n| `n9-fundi` | `mzizi-fundi` | `mzizi-roots-server` (`fundi`) | the Fundi reporter and learning loop (client side) |\n| `n10-documentation` | `mzizi-docs` | `mzizi-roots-server` (`docs`) | the docs API and routing, AI context, the changelog and docs renderers (brings in `dioxus`) |\n| `n11-discovery` | `mzizi-discovery` | `mzizi-roots-server` (`discovery`) | page metadata and Schema.org JSON-LD |\n\nN4 safety, N5 resilience and N6 pages have no crate yet. When they do (`mzizi-safety`,\n`mzizi-resilience`, `mzizi-pages`), they join `mzizi-roots` as features. A Workers adapter\n(`mzizi-worker`) is planned for `mzizi-roots-server` and has not landed.\n\nThe registry has 577 items and 55 `.rs` files. Cite `GET https://api.mzizi.dev/v1/ui` or\n`/v1/stats` for a current count rather than repeating these. To find a component's crate, read\n`rust.crate` from `mzizi_get_component`, or take its node directory and read the table above.\n`https://api.mzizi.dev/v1/rs/<name>` also serves a `crate` field, derived from the same map on\nregistry `main`; the API serves the registry at a pinned commit, and until the gateway's pin\nmoves past the map it names `mzizi-ui` for every component. Trust the map, or the MCP.\n\n## Using a UI component\n\nThis compiles against `mzizi-roots` 0.1.0 from crates.io and renders the component to a static\nHTML fragment with `dioxus-ssr`:\n\n```toml\n[dependencies]\nmzizi-roots = { version = \"0.1\", default-features = false, features = [\"brand\"] }\ndioxus = { version = \"0.7\", default-features = false, features = [\"macro\", \"signals\", \"hooks\", \"html\"] }\ndioxus-ssr = \"0.7\"\n```\n\n```rust\nuse dioxus::prelude::*;\nuse mzizi_roots::brand::{AlertSeverity, MziziAlertBanner};\n\nfn banner() -> Element {\n rsx! {\n MziziAlertBanner {\n r#type: \"Thunderstorm\",\n severity: AlertSeverity::Severe,\n headline: \"Severe storms this afternoon\",\n }\n }\n}\n\nfn main() {\n let mut dom = VirtualDom::new(banner);\n dom.rebuild_in_place();\n let html = dioxus_ssr::render(&dom); // a static HTML fragment for Astro to place\n println!(\"{html}\");\n}\n```\n\nThe output is a `<div data-slot=\"mzizi-alert-banner\" data-portal=\"https://mzizi.dev/components/mzizi-alert-banner\" role=\"alert\" …>`\nwith the same Tailwind classes and CSS custom properties as the React build, so one stylesheet\nstyles both.\n\nWhat a Roots UI component looks like:\n\n- **Same contract as the React build:** the same registry name, `data-slot`, `data-portal`,\n variant names and Tailwind classes over the same `var(--…)` tokens. No colour literals.\n- **Variants are enums**, and a `match` replaces `class-variance-authority`\n (`n2-primitives/badge.rs`, `button.rs`).\n- **Browser facilities are props.** Where the `.tsx` reads `useMziziHarness`, the Rust takes the\n one bit it branches on, usually `prefers_reduced_motion: bool`. Logging is the host's.\n- **Icons are slots:** an `Element` the host renders, where the `.tsx` takes a Lucide component.\n- **Each file is self-contained.** No helpers shared between components.\n\n## The two frontend paths\n\nThe frontend is either **Astro with Roots underneath** or **pure Rust end to end**.\n\n- **Pure Rust.** A Dioxus app depends on the crates and mounts the components on web (wasm),\n desktop or mobile. Every component supports this today.\n- **Astro, static.** Render with `dioxus-ssr` at build time or in a Worker, and let Astro place\n the fragment, as in the example above. It ships no JavaScript and suits display components.\n The brand crate's contract suite does this for every component on every run.\n- **Astro, interactive islands.** Compile to wasm with Dioxus's web renderer and wrap it as a\n custom element. **Not packaged yet**: the wrapper crate (`mzizi-islands`) is proposed for\n batch 2 of the RFC.\n\nAstro has no `.astro` components to install. `mzizi add` refuses an Astro target and names the\ngap. There is no React, Svelte or Vue layer under Astro.\n\n## Starting an app\n\nFor a new Mzizi-ecosystem app, or any app that consumes the design system. Pick the path\nfirst (above); these steps are the Astro one, with the pure-Rust differences noted.\n\n### 1. Create the project (Astro)\n\n```bash\npnpm create astro@latest my-app -- --template minimal --typescript strict\ncd my-app\npnpm astro add tailwind cloudflare\n```\n\nAstro renders HTML by default, which is what makes a new app indexable by N11 and paintable on\na low-end device without opting in to either. For a pure-Rust app, start from\n`cargo new my-app` and a Dioxus 0.7 app instead, and skip to step 3.\n\n### 2. Wire the tokens\n\nCopy the generated stylesheet rather than retyping hexes: `skills/mzizi-design/tokens/palette.css`\nin `@nyuchi/mzizi-skills` (all 21 families, light and dark, generated from\n`palette.canonical.json` at the package root). Import it from your global stylesheet (in Astro,\n`src/styles/global.css`) and register the properties in the Tailwind 4 `@theme` block so\nutilities generate. In Rust the same palette is the `mzizi-tokens` crate, always on in\n`mzizi-roots`. How the 21 families are meant to be used, the radius scale and the 48px floor\nare in the `mzizi-design` skill.\n\nPick the app's accent through `--brand-accent` rather than hardcoding a mineral hex: nyuchi =\ngold, mukoko = tanzanite.\n\n### 3. Add the Roots components\n\n```bash\ncargo add mzizi-roots # tokens, primitives, brand components, app shell\n```\n\nFor Astro, a small Rust crate (a build step, or a `workers-rs` Worker for request-time\nrendering) renders each component to HTML with `dioxus-ssr`, and Astro places the fragment.\nThe fragment carries the same Tailwind classes as the React build, so the stylesheet from step 2\nstyles it, and it ships no JavaScript. Interactive islands (Roots compiled to wasm and wrapped\nas custom elements) are proposed and not packaged yet. \"Using a UI component\" above has a\ncompiling example.\n\nNever copy component source between apps. Depend on the crate, so every app inherits fixes.\n\n### 4. Give the app's AI assistants the doctrine\n\nInstall the published skills bundle so any assistant working in the repo has the design system,\nthe language and the components on hand:\n\n```bash\nnpm install -D @nyuchi/mzizi-skills\nnpx skills experimental_sync # links the bundled skills into .agents/skills/\n```\n\n`npx skills add @nyuchi/mzizi-skills` does **not** work: the `skills` CLI reads that argument\nas a GitHub repository and fails to clone it. `experimental_sync` is the CLI's route for skills\nthat ship in `node_modules`, and it is marked experimental. The same skills are served with no\ninstall at all by the MCP's `mzizi_get_skills`.\n\nThe public Claude Code plugin installs these skills and the MCP in one step:\n\n```\n/plugin marketplace add mzizi-dev/mzizi-registry\n/plugin install mzizi@mzizi\n```\n\nTo wire the MCP directly instead, add it to `.mcp.json` or your client config. It is free with\nno account; only the Fundi tools need sign-in:\n\n```json\n{\n \"mcpServers\": {\n \"mzizi\": { \"type\": \"http\", \"url\": \"https://mcp.mzizi.dev/mcp\" }\n }\n}\n```\n\n### 5. Let fundi read the project\n\n`@nyuchi/mzizi-cli` ships the **fundi** agent, which reads the project off disk and plans the\nwiring against the registry:\n\n```bash\npnpm add -D @nyuchi/mzizi-cli\npnpm exec fundi explore # offline — snapshot the project\npnpm exec fundi plan \"wire up Mzizi tokens\"\n```\n\nRun the bins from the installed package (`pnpm exec`, or `npx` inside the project). A bare\n`npx fundi` outside it fetches an unrelated npm package called `fundi`. `explore` runs without\ncredentials; `plan` and `chat` need `ANTHROPIC_API_KEY`. A plan is\ninert until you apply it, so read it before letting it write.\n\n### 6. Verify\n\n```bash\npnpm dev\n```\n\nConfirm the tokens resolve in light and dark, a rendered Roots fragment picks up the styles,\nand the page is readable with JavaScript off.\n\n### 7. An existing React app\n\nIf the app is already React, the React build installs with the CLI or shadcn, by absolute URL:\n\n```bash\npnpm add -D @nyuchi/mzizi-cli && pnpm exec mzizi add button # infers the tsx target\nnpx shadcn@latest add https://api.mzizi.dev/v1/ui/mzizi-theme-provider\n```\n\nPoint `components.json` `registries.mzizi.url` at `https://api.mzizi.dev/v1/ui`. A bare name\nresolves against shadcn's own registry and installs a different component. Do not start a new\napp this way.\n\n## Server components\n\nA Roots server component is a **sans-IO core**: typed request in, typed response out, every\nexternal facility (a store, a clock, an outbound fetch, a secret) behind a trait. The host owns\nI/O and credentials. `n10-documentation/mzizi-docs-api.rs` is the model: it parses a path into a\ntyped `Route`, asks a `DocsStore` trait for data and returns an `ApiResponse`. `mzizi-assurance`,\n`mzizi-fundi` and `mzizi-discovery` have no dependencies at all. The RFC records a passing\n`wasm32-unknown-unknown` `cargo check` for those three and `mzizi-docs`; CI does not run a\nwasm32 job yet.\n\nA host adapter mounts the core: a `workers-rs` adapter for Cloudflare Workers, an axum adapter\nfor containers. Neither adapter crate exists yet. Until then the host writes the few lines that\nturn its request into the core's request (see the `mzizi-backend` skill). Dioxus\n`#[server]` functions are one more possible adapter, not the building block.\n\n## Contracts\n\nEvery Roots component carries a contract, in two parts:\n\n1. **The registry contract:** one registry name, description, dependencies and variants shared\n by both builds, and the same `data-slot`, `data-portal` and tokens. Each crate's\n `tests/contract.rs` reads the `.tsx` sibling and asserts that the slot, the portal and every\n variant value the Rust emits appear there too. A deliberate divergence, such as a touch\n target raised to the 48px floor, is asserted from both sides, so the test fails when the\n React build is fixed.\n2. **Checkable clauses** in the Mzizi language's `contract … end` grammar (RFC-0006; the\n `mzizi-language` skill), exported as `pub const CONTRACT: &str`. From\n `n3-brand/mzizi-hero-stat.rs`:\n\n ```rust\n pub const CONTRACT: &str = r#\"contract\n slot is \"mzizi-hero-stat\"\n role is \"region\"\n label not_empty\n button \"Share\" min_height 48\n end\"#;\n ```\n\n `mzizi-brand`'s contract suite renders each component with `dioxus-ssr` in named states and\n evaluates every clause against the markup it emits. **A clause it cannot evaluate fails.**\n `mzizi_roots::brand::CONTRACTS` lists every brand component with its contract.\n\nToday the twelve `mzizi-brand` components export `CONTRACT`. The other crates check the\nregistry contract against the `.tsx`, and their `CONTRACT` clauses arrive batch by batch.\nServer-component clauses (status per route, CORS and cache headers, no credential in the core)\nwait on the language's contracts-everywhere RFC. Until then their tests assert those properties\nagainst an in-memory implementation of the traits. When the language lowers to Rust, `mz\ncontract` reads the same text; it does not yet.\n\n## Reuse before build\n\n**The simplest change is the one you don't write.** Reuse before you build, compose before you\nimplement, and let one source own each decision. Use this before adding a component, a style or\na page, and during any refactor.\n\n1. **Reuse before build.** Search the registry first: `mzizi_search` or\n `mzizi_list_components` over the MCP (`https://mcp.mzizi.dev/mcp`, free), or\n `GET https://api.mzizi.dev/v1/ui`. Where a Rust implementation exists, depend on its crate\n (`cargo add mzizi-roots`). For the React build, install with\n `mzizi add <name>` or `npx shadcn@latest add https://api.mzizi.dev/v1/ui/<name>`. Never\n reimplement a primitive that already exists.\n2. **Styles are tokens.** Never hardcode a colour, radius, spacing, or motion value —\n consume `var(--…)` (or the `mzizi-tokens` consts in Rust). N1 is the only layer allowed to\n define values, so a rebrand is one edit. A new colour goes in the registry's\n `lib/tokens/palette.source.ts`, and `pnpm tokens:sync` regenerates `globals.css`, the Rust\n tokens and the other emitters from it.\n3. **Compose, don't implement.** A page (N6) is a composition of N2 primitives and N3 brand\n components — never inline a `<button>`, a card, or an SVG. Pull spinners, skeletons, and\n badges from N2.\n4. **Variants over copies.** Collapse near-duplicate components into one variant table: an\n enum with a `match` in Rust, an `enum` table in Mzizi source, one `cva()` in the React\n build. Express differences as `variant` / `size` props, not new files.\n5. **One composition path.** In the React build, compose class names through `cn()` and\n extract shared logic into `lib/` or a hook. In Rust, a pure function beside the component.\n No logic duplicated across files.\n6. **Right altitude.** Keep each component self-contained, importing only from the layer\n below on its strand. A primitive that reaches up into page code can't be installed on its\n own.\n7. **Delete first.** Prefer removing over adding — dead code, unused props, redundant\n wrappers, commented-out blocks. Less code is less to break, read, and keep true.\n8. **Live data, not copies.** Read counts, tokens and doctrine from their source\n (`https://api.mzizi.dev/v1/stats`, `palette.canonical.json`, `mzizi_get_tokens`,\n `mzizi_get_doctrine`). A baked-in number or duplicated token guarantees drift.\n\n### Checklist\n\n- [ ] Searched the registry before building, and used the Rust build where one exists?\n- [ ] All styles are tokens (`var(--…)`), zero hardcoded values outside N1?\n- [ ] Page is pure composition of N2/N3, no inlined primitives?\n- [ ] Variation expressed as one variant table, not near-duplicate files?\n- [ ] One composition path, shared logic extracted once?\n- [ ] Each file self-contained and installable, importing only downward?\n- [ ] Deleted more than you added where you could?\n- [ ] Counts, tokens, and doctrine read live, not hardcoded?\n\n## Contributing a component\n\n**A component is a real file.** `components/registry/n<N>-<label>/<name>.rs` (Rust, compiled by\nthe node's crate in `mzizi-rs/`) and, for the React build, `<name>.tsx` beside it, which the\nNext.js build compiles and typechecks. An error in either fails CI.\n\n| File | What it is |\n| --------------------------------------------- | ----------------------------------------------------------------- |\n| `components/registry/n<N>-<label>/<name>.rs` | the Roots component (Dioxus UI, or a sans-IO server core) |\n| `components/registry/n<N>-<label>/<name>.tsx` | the React build of the same contract |\n| `registry.json` | the manifest `/v1/ui/<name>` and `npx shadcn add` resolve against |\n\n`registry.json` carries the install contract (description, dependencies,\n`registryDependencies`, target paths) and **never the source**. The node is derived from the\ndirectory the component lives in, so it cannot disagree with where the file is.\n\n**There is no database.** The registry's files are its data layer (mzizi-registry PR #368), and\na test fails the build if a Supabase import or a `SUPABASE_*` read comes back. Nothing about a\ncomponent, including its source, status or history, is written to a table. If you find\ninstructions anywhere telling you to `INSERT` a component or read a `component_documents` row,\nthey are stale.\n\n### Step 1 — Decide the node\n\nThe architecture is the **Mzizi DNA double helix**: eight nodes (N1–N8) on the engineering\nbackbone, four rungs (N9–N12) that bridge both backbones, and six strands. The N-numbers are\n**labels, not a sequence**.\n\nThe node decides the **directory**, the directory decides the **crate** that compiles the Rust\n(`mzizi-rs/crate-for-node.json`), and the directory is what the build reads. Choosing the node\nand choosing where the file goes are the same decision.\n\n| Node | Directory | Crate | What belongs there |\n| ----------------- | -------------------- | ----------------- | --------------------------------------------------------------------------- |\n| N1 tokens | `n1-tokens/` | `mzizi-tokens` | A token value or emitter (colour, spacing, motion, radius), not a component |\n| N2 primitive | `n2-primitives/` | `mzizi-ui` | Generic UI: button, card, input, dialog, toolbar, bento-grid |\n| N3 brand | `n3-brand/` | `mzizi-brand` | A branded composition with the mineral palette |\n| N4 safety | `n4-safety/` | none yet | A conditional-rendering gate: permission, geo, rate limit |\n| N5 resilience | `n5-resilience/` | none yet | Error boundary, skeleton, offline banner, fallback chain |\n| N6 pages | `n6-pages/` | none yet | Full-screen layout composition |\n| N7 shell | `n7-shell/` | `mzizi-shell` | App container: navigation, routing, lifecycle |\n| N8 assurance | `n8-assurance/` | `mzizi-assurance` | Instrumentation, a11y audit, RTL conformity, probes |\n| N9 fundi | `n9-fundi/` | `mzizi-fundi` | Automated healing (a rung) |\n| N10 documentation | `n10-documentation/` | `mzizi-docs` | Docs, AI instructions, docs infrastructure (a rung) |\n| N11 discovery | `n11-discovery/` | `mzizi-discovery` | SEO / AIO surfaces: if the machine can't see it, it doesn't exist (a rung) |\n| N12 skills | none in the registry | none | Agent skills (a rung), authored in `mzizi-dev/agent-tools/mzizi-skills/` |\n\nA node with no crate yet (N4, N5, N6) takes a `.tsx` today. Adding its first `.rs` means adding\nits crate and its `crate-for-node.json` entry in the same pull request; `pnpm rust:generate`\nrefuses Rust that no crate claims.\n\nIf you are unsure where something belongs, ask the MCP (`https://mcp.mzizi.dev/mcp`, free, no\naccount): `mzizi_list_components` with `node: N` shows what already lives in a node, and\n`mzizi_get_architecture` returns the node, rung and strand model with live counts. Never\nhardcode counts.\n\n### Step 2 — Write the file\n\nCreate it in the directory the node names:\n\n```\ncomponents/registry/n3-brand/mzizi-your-component.rs # the Roots component\ncomponents/registry/n3-brand/mzizi-your-component.tsx # the React build, same contract\n```\n\nThe Rust file carries `pub const CONTRACT: &str` and follows the rules in \"Using a UI component\" above: variants as enums, tokens by reference, browser facilities as props, icons as slots,\nno helpers shared between components. `.ts` is for a non-rendering module.\n\nThat is the authoring step. There is no row to insert and no registration to perform before\nwriting code: the file _is_ the component, and `cargo test` (for the `.rs`), `pnpm typecheck`\nand `pnpm build` (for the `.tsx`) are what tell you it is real.\n\nNaming: N3 brand components take a `mzizi-` prefix. (Components formerly named `nyuchi-*` are\n`mzizi-*`, and the old names redirect.) Everything else is the bare name, matching what a\nconsumer will type in `mzizi add` or `npx shadcn add`.\n\n### Wiring the Rust into its crate\n\n`pnpm rust:generate` writes the committed copy to `mzizi-rs/crates/<crate>/src/generated/`. Add\nthe `#[path = \"generated/<name>.rs\"] pub mod <name_snake>;` line and the `pub use` to the\ncrate's `src/lib.rs` by hand, and add the component to the crate's `tests/contract.rs`. Edit\nthe registry file, never the copy. The workspace lints forbid `unsafe_code` and warn on\n`missing_docs`; clippy turns the warning into a failure. Cap parallel builds on a small machine\n(`CARGO_BUILD_JOBS=2`).\n\nA release is a version bump in `mzizi-rs/Cargo.toml`. On merge to `main`,\n`.github/workflows/publish-crates.yml` re-runs the Rust gate and publishes each crate in\ndependency order, skipping any version already on crates.io. A published crate name is\npermanent. Say in the PR body that components changed, so the site, docs and skills are\nupdated to match.\n\n### Step 3 — Declare it in `registry.json`\n\nOne item, carrying the install contract. `meta` is the part you author; four other fields are\n**derived from it** and must not be hand-written.\n\n```jsonc\n{\n \"name\": \"your-component-name\",\n \"type\": \"registry:ui\",\n \"description\": \"Short description of what it does.\",\n \"dependencies\": [],\n \"registryDependencies\": [],\n \"files\": [\n {\n \"path\": \"components/registry/n2-primitives/your-component-name.tsx\",\n \"target\": \"components/ui/your-component-name.tsx\",\n \"type\": \"registry:ui\"\n }\n ],\n \"meta\": {\n \"collection\": \"primitives\",\n \"owner\": \"mzizi\",\n \"useCases\": [\"Use case 1\", \"Use case 2\"],\n \"variants\": [\"default\", \"sm\", \"lg\"],\n \"sizes\": [\"default\", \"sm\", \"lg\"],\n \"features\": [\"cn() for className composition\", \"data-slot attribute\"],\n \"a11y\": [\"ARIA attributes\", \"Keyboard accessible\"],\n \"hasDemo\": true\n }\n}\n```\n\n`files[]` lists the `.tsx`: it is the shadcn install contract for the React build. The `.rs`\nbeside it is not listed; the node's crate compiles it, and `/v1/rs/<name>` serves it with the\ncrate's name.\n\n**Do not write `title`, `categories`, `docs` or `author`.** `pnpm registry:metadata` derives all\nfour from `meta` and the repo. `docs` in particular is what the shadcn CLI prints _after_\ninstalling, so it is generated from your `useCases` / `variants` / `sizes` / `features` / `a11y`\nrather than duplicated by hand — and an item with no `meta` deliberately gets no `docs`, because\na stub reads as documentation and says nothing.\n\nThen run the generators and let the gates check you:\n\n```bash\npnpm registry:paths # fill file paths from what is on disk\npnpm registry:metadata # derive title, categories, docs, author\npnpm registry:normalize # canonical ordering\npnpm registry:validate # every item resolves on disk; every dependency addressable\npnpm registry:verify # the CI drift gate\npnpm typecheck && pnpm build\n```\n\nFollow the criteria for the chosen node.\n\n#### Every node: the Rust file\n\n- `pub const CONTRACT: &str` with the clauses the rendered markup must meet (slot, role, touch\n floors, variant tables), evaluated by the crate's `tests/contract.rs` on `dioxus-ssr` output\n- The same `data-slot`, `data-portal` and variant values as the `.tsx`, asserted by the same\n suite; a deliberate divergence is asserted from both sides\n- `prefers_reduced_motion: bool` as a prop where the `.tsx` reads the harness's motion\n- Icons as `Element` slots, not an icon crate\n- Doc comments on every public item (`missing_docs` is a warning, and clippy runs with\n `-D warnings`); no `unsafe`\n\n#### N2 primitive checklist\n\n- No `useMziziHarness` import in the `.tsx`\n- `data-slot` attribute on the root element\n- `data-portal` attribute pointing at `https://mzizi.dev/components/<name>`\n- `cn()` for className composition in the `.tsx`\n- No raw Tailwind colours: use semantic tokens (`bg-primary`, `text-foreground`, …)\n- Icons from `@/lib/icons` in the `.tsx`, never `lucide-react` directly\n- **Touch targets never go below 48px.** Buttons and inputs are `h-14` (56px) default and\n `h-12` (48px) small, as `button.tsx`, `button.rs` and `primitives/button.mz` all state, and\n `/v1/brand` `.componentSpecs` serves (`minTouchTarget: 48`). A smaller visual box, such as a\n badge, is not a touch target; if it becomes one, give it `min-h-[48px]`.\n- Pill-shaped categories (button, input, avatar, badge, toggle) use `rounded-full`\n\n#### N3 brand checklist\n\n- `mzizi-` prefix on the name\n- `useMziziHarness(\"<name>\")` in the `.tsx` for the motion preference (and `log` or\n `LiveRegion` where the component uses them); `prefers_reduced_motion` as a prop in the `.rs`\n- The entrance animation off when motion is reduced\n- An ARIA role or `aria-label`, and a region that is named in every state, the loading\n skeleton included\n- `data-slot` and `data-portal` attributes, in every state\n- `focus-visible` ring on interactive elements, and interactive elements at the 48px floor\n- I18N via `Intl` formatters for dates, numbers and currency\n- Semantic tokens for status-bearing colours\n\n#### N6 page checklist\n\n- Pure composition: no inline buttons, cards or SVGs\n- Accept children/slots for content\n- Semantic CSS vars only (`bg-card`, `text-foreground`, `bg-primary`)\n- A loading-state prop\n- `role=\"main\"` and an `aria-label`\n\n#### N8 assurance checklist\n\n- A sans-IO Rust core (typed input, typed result, every facility behind a trait) in\n `mzizi-assurance`, with the `.ts` module as the React build\n- A banner comment identifying the node and purpose\n- A configurable rules array\n- Optional `onViolation` / `onComplete` callbacks\n\n### Step 4 — Docs and demos\n\nThere is nothing extra to write. Docs come from the `meta` you already authored in Step 3\n(`useCases`, `variants`, `sizes`, `features`, `a11y`), and `pnpm registry:metadata` turns them\ninto the `docs` string the CLI prints after install. Set `meta.hasDemo` when a demo exists.\n\nIf a prop table is expected, `pnpm props:extract` derives it from the component's own types and\n`pnpm props:verify` is the gate. Props are read from the code, never restated beside it.\n\n### Step 5 — Versions and history\n\nThere is no status row and no database. A component's version history is the registry's\nchangelog (`content/changelog/`, generated, checked by `pnpm changelog:generate:check`) and the\nrename map. `mzizi_get_component` with `include: [\"versions\"]` returns it. Read what a component\nis through the MCP or `https://api.mzizi.dev/v1/ui/<name>` rather than assuming.\n\n### Step 6 — Validate accessibility\n\nIf your component introduces a new colour pair, validate it before shipping. The MCP tool is\ncomputed locally and needs no account:\n\n```\nmzizi_check_accessibility(foreground: \"#FFFFFF\", background: \"#0047AB\")\n```\n\nIt returns the WCAG 2.1 contrast ratio with AA/AAA verdicts and whether the pair clears the\nMzizi floor (APCA and AAA).\n\n### Step 7 — Before you open the PR\n\n`pnpm check` runs every registry gate CI runs, in order, including `registry:validate`,\n`registry:verify`, `tokens:verify`, `changelog:generate:check`, `rust:generate:check`, the\ntests and the build. Then the Rust workspace gate:\n\n```bash\npnpm check\ncargo fmt --manifest-path mzizi-rs/Cargo.toml --all -- --check\ncargo clippy --manifest-path mzizi-rs/Cargo.toml --workspace --all-targets -- -D warnings\ncargo test --manifest-path mzizi-rs/Cargo.toml --workspace\ncargo package --manifest-path mzizi-rs/Cargo.toml --workspace\n```\n\nIf you touched `files[]` or `meta`, run `pnpm registry:paths`, `pnpm registry:metadata` and\n`pnpm registry:normalize` first; their `--check` twins fail otherwise.\n\nA green run means the item resolves to real files, every dependency is addressable, the derived\nfields match `meta`, the Rust compiles, and its contract holds on rendered markup. Say in the PR\nbody that a component changed, so the site, the docs and the skills are updated to match.\n\n### What to avoid\n\n- **Never put component source in a database row, a JSON document, or any second place.** The\n file under `components/registry/` is the component; everything else is a representation of\n it, and a representation can drift from the code while still looking correct. That is not\n hypothetical here: a `source_code` column once served stale copies for 571 components.\n- Never edit `mzizi-rs/crates/*/src/generated/`. It is overwritten by `pnpm rust:generate`.\n- Never hand-write `title`, `categories`, `docs` or `author` in `registry.json`. They are\n derived, and a hand-written value is a second source that the next generator run silently\n overwrites.\n- Never hardcode hex colour values (except documented third-party brands like Ethereum,\n Google, EcoCash).\n- Never use `lucide-react` directly in a `.tsx`; go through `@/lib/icons`.\n- Never use `margin-left` / `padding-right` / `left`; use logical properties\n (`margin-inline-start`, …) for RTL support.\n- Never start new work in the React build alone. If a component has a Rust implementation,\n change the Rust and keep the `.tsx` in step.\n- Never bump a major version on your own initiative. That is a maintainers' call.\n\n## The React build\n\n`npx shadcn@latest add https://api.mzizi.dev/v1/ui/<name>` installs the `.tsx` for an existing\nReact app, for all 577 items. Always pass the absolute URL: a bare name resolves against\nshadcn's own registry and installs a different component. `mzizi add <name>` installs the React\nbuild too. Neither is where new work goes.\n\n## See also\n\n- `docs/roots/RFC-roots.md` in the registry: the inventory, the conversion batches and what\n the API and site still need for Rust-first serving (`/v1/roots` is proposed, not live).\n- The `mzizi-language` skill for the clause grammar and `mz contract`.\n- The `mzizi-design` skill for the palette, radius, type and the Ubuntu constraints.\n- The `mzizi-backend` skill for mounting a server core on Workers.",
21
+ "category": "build",
22
+ "description": "Everything about Mzizi's components: Mzizi Roots, the Rust build (Dioxus UI and host-agnostic server components). cargo add mzizi-roots / mzizi-roots-server, what mzizi_get_component returns (Rust first: lead, rust, react), the node-to-crate map, pub const CONTRACT checked on dioxus-ssr markup, the component contracts in the registry's contracts/ for the Mzizi Dashboard Standard (app/, 31) and the Mzizi Discover Standard (discover/, 16 with the detail pattern and the canonical Open in Mukoko link), site/ (8) and ui/ (4): what to read before building or changing a @bundu/ui component, the no-JS fallback and density as requirements, and how Discover differs (no script, the same size on every pointer, server-filled shells), the frontend paths (pure .astro from the registry via mzizi add --target astro, Astro with Roots, or pure Rust; Next.js adopts through the registry's .tsx) and app setup, reuse before build, and adding a component to the registry (the node, the .rs and .tsx, registry.json, per-node checklists, the 48px floor, pnpm check and the cargo gates). The React/TSX build is the deprioritised secondary. Replaces scaffold-component, simplify and half of ecosystem-app-setup.",
23
+ "body_mdx": "# Mzizi Roots — the Rust build of Mzizi's components\n\n**Mzizi Roots** is Mzizi's own components in Rust: UI components in Dioxus 0.7, and server\ncomponents as host-agnostic Rust. They are built to support the Mzizi language (the\n`mzizi-language` skill) as its component model, the way React is JavaScript's; they are not\nthe language. They live in\n[`mzizi-dev/mzizi-registry`](https://github.com/mzizi-dev/mzizi-registry) and are compiled by\nthe crates in its `mzizi-rs/` workspace. The plan is `docs/roots/RFC-roots.md` there.\n\n**Rust first.** Where a Rust implementation exists, present it and use it first. The React/TSX\ncomponents are **the React build**: they keep working for existing React apps, and they are\ndeprioritised, so no new work goes into them.\n\nThis skill describes registry `main` at `6663cb5` (2026-09-29) and its `contracts/` at\n`b17bafc` (2026-10-04: the Dashboard Standard from #406, the Discover Standard from #415, and\nevery `since` at `@bundu/ui` 0.3.0 from #419), the crates published to crates.io at **0.1.0**\non 2026-09-29, and `mzizi-mcp` 0.11.0. It replaces three 0.7.0 skills:\n`scaffold-component` (now \"Contributing a component\"), `simplify` (now \"Reuse before build\")\nand the frontend half of `ecosystem-app-setup` (now \"Starting an app\"). Tokens, brand and the\nUbuntu constraints are in the `mzizi-design` skill.\n\n## Install\n\nTwo umbrella crates with no code of their own re-export the node crates behind features:\n\n```sh\ncargo add mzizi-roots # UI: tokens + ui + brand + shell (all default features)\ncargo add mzizi-roots-server # server: assurance + fundi + docs + discovery\n```\n\nTake only what you use:\n\n```toml\n[dependencies]\nmzizi-roots = { version = \"0.1\", default-features = false, features = [\"brand\"] }\n```\n\nTo follow `main`, or pin a commit, install from git. Cargo finds a crate by name anywhere in the\nrepository's workspace:\n\n```sh\ncargo add mzizi-roots --git https://github.com/mzizi-dev/mzizi-registry\n```\n\n```toml\nmzizi-brand = { git = \"https://github.com/mzizi-dev/mzizi-registry\", rev = \"<commit>\" }\n```\n\nA single node crate works too (`cargo add mzizi-ui`). The crates depend on `dioxus` with\n`macro`, `signals`, `hooks` and `html` only, so the app that mounts them chooses web, desktop,\nmobile or server rendering.\n\n`mzizi add <name> --target rust` (`@nyuchi/mzizi-cli`) records the crate and prints the\n`cargo add` rather than copying a `.rs` file into your project: a copied component is a fork\nof a crate module.\n\n## What the MCP returns\n\n`mzizi_get_component` on `https://mcp.mzizi.dev/mcp` (free, no account; `mzizi-mcp` 0.11.0) is\nRust first. It returns `{name, renamedFrom?, lead, note, rust, react, docs?, versions?}`:\n\n- `lead` is `\"rust\"` when the component has a Roots implementation, else `\"react\"`; `note`\n says which build to use.\n- `rust` is the registry's `/v1/rs/<name>` answer with the install first: `crate`\n (`{name, registry, git}`), `install.cargo` (`cargo add <crate>`), `install.pinned`\n (`cargo add <crate> --git https://github.com/mzizi-dev/mzizi-registry --rev <pin>`, which\n builds exactly the source shown), `module` (e.g. `mzizi_brand`), `contract` (the\n `contract … end` text, or `null`), and `files[]` with the `.rs` source inline. With no Rust\n it is `null`.\n- `react` is the React build: `build: \"react\"`, `status: \"deprioritised\"`, `install` (the\n `npx shadcn@latest add https://api.mzizi.dev/v1/ui/<name>` line), then the `/v1/ui/<name>`\n body. It is no longer under a `component` key (that was 0.10.x).\n\n`mzizi_list_components` rows carry `rustCrate` when a component has Rust, the response carries\n`withRust`, and `rust: true` lists only those. `mzizi_search` hits carry `rustCrate` too. For\n`mzizi-hero-stat` the answer is `lead: \"rust\"`, crate `mzizi-brand`, and the four-clause\ncontract shown under \"Contracts\" below.\n\n## The crates\n\nOne crate per registry node that has Rust. The map is `mzizi-rs/crate-for-node.json`, and\n`pnpm rust:generate` refuses a node with Rust in it that no crate claims.\n\n| Node directory | Crate | Umbrella | What it holds |\n| ------------------- | ----------------- | ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `n1-tokens` | `mzizi-tokens` | `mzizi-roots` (always on) | the 21-family palette and scales as Rust consts, generated from `lib/tokens/palette.source.ts` |\n| `n2-primitives` | `mzizi-ui` | `mzizi-roots` (`ui`) | avatar, badge, button, card, chart, input, label, progress, separator |\n| `n3-brand` | `mzizi-brand` | `mzizi-roots` (`brand`) | alert banner, avatar stack, cover header, empty state, escalation card, gauge card, hero stat, meta tile, stats row, success screen, suitability card, user card (all `mzizi-*`) |\n| `n7-shell` | `mzizi-shell` | `mzizi-roots` (`shell`) | bottom nav, command palette, connectivity bar, deep-link handler, footer, mini-app runtime, notification center, persistent player, route guard, theme provider, toast provider, update prompt |\n| `n8-assurance` | `mzizi-assurance` | `mzizi-roots-server` (`assurance`) | probes, telemetry, the OTLP exporter, a11y and RTL conformity checks, alert and incident engines |\n| `n9-fundi` | `mzizi-fundi` | `mzizi-roots-server` (`fundi`) | the Fundi reporter and learning loop (client side) |\n| `n10-documentation` | `mzizi-docs` | `mzizi-roots-server` (`docs`) | the docs API and routing, AI context, the changelog and docs renderers (brings in `dioxus`) |\n| `n11-discovery` | `mzizi-discovery` | `mzizi-roots-server` (`discovery`) | page metadata and Schema.org JSON-LD |\n\nN4 safety, N5 resilience and N6 pages have no crate yet. When they do (`mzizi-safety`,\n`mzizi-resilience`, `mzizi-pages`), they join `mzizi-roots` as features. A Workers adapter\n(`mzizi-worker`) is planned for `mzizi-roots-server` and has not landed.\n\nThe registry has 577 items and 55 `.rs` files. Cite `GET https://api.mzizi.dev/v1/ui` or\n`/v1/stats` for a current count rather than repeating these. To find a component's crate, read\n`rust.crate` from `mzizi_get_component`, or take its node directory and read the table above.\n`https://api.mzizi.dev/v1/rs/<name>` also serves a `crate` field, derived from the same map on\nregistry `main`; the API serves the registry at a pinned commit, and until the gateway's pin\nmoves past the map it names `mzizi-ui` for every component. Trust the map, or the MCP.\n\n## Using a UI component\n\nThis compiles against `mzizi-roots` 0.1.0 from crates.io and renders the component to a static\nHTML fragment with `dioxus-ssr`:\n\n```toml\n[dependencies]\nmzizi-roots = { version = \"0.1\", default-features = false, features = [\"brand\"] }\ndioxus = { version = \"0.7\", default-features = false, features = [\"macro\", \"signals\", \"hooks\", \"html\"] }\ndioxus-ssr = \"0.7\"\n```\n\n```rust\nuse dioxus::prelude::*;\nuse mzizi_roots::brand::{AlertSeverity, MziziAlertBanner};\n\nfn banner() -> Element {\n rsx! {\n MziziAlertBanner {\n r#type: \"Thunderstorm\",\n severity: AlertSeverity::Severe,\n headline: \"Severe storms this afternoon\",\n }\n }\n}\n\nfn main() {\n let mut dom = VirtualDom::new(banner);\n dom.rebuild_in_place();\n let html = dioxus_ssr::render(&dom); // a static HTML fragment for Astro to place\n println!(\"{html}\");\n}\n```\n\nThe output is a `<div data-slot=\"mzizi-alert-banner\" data-portal=\"https://mzizi.dev/components/mzizi-alert-banner\" role=\"alert\" …>`\nwith the same Tailwind classes and CSS custom properties as the React build, so one stylesheet\nstyles both.\n\nWhat a Roots UI component looks like:\n\n- **Same contract as the React build:** the same registry name, `data-slot`, `data-portal`,\n variant names and Tailwind classes over the same `var(--…)` tokens. No colour literals.\n- **Variants are enums**, and a `match` replaces `class-variance-authority`\n (`n2-primitives/badge.rs`, `button.rs`).\n- **Browser facilities are props.** Where the `.tsx` reads `useMziziHarness`, the Rust takes the\n one bit it branches on, usually `prefers_reduced_motion: bool`. Logging is the host's.\n- **Icons are slots:** an `Element` the host renders, where the `.tsx` takes a Lucide component.\n- **Each file is self-contained.** No helpers shared between components.\n\n## The two frontend paths\n\nThe frontend is either **Astro** (pure `.astro` components, or Roots rendered underneath) or\n**pure Rust end to end**.\n\n- **Pure Rust.** A Dioxus app depends on the crates and mounts the components on web (wasm),\n desktop or mobile. Every component supports this today.\n- **Astro, static.** Render with `dioxus-ssr` at build time or in a Worker, and let Astro place\n the fragment, as in the example above. It ships no JavaScript and suits display components.\n The brand crate's contract suite does this for every component on every run.\n- **Astro, interactive islands.** Compile to wasm with Dioxus's web renderer and wrap it as a\n custom element. **Not packaged yet**: the wrapper crate (`mzizi-islands`) is proposed for\n batch 2 of the RFC.\n\n- **Astro, pure `.astro` components** (mzizi-registry #397, owner decision 2026-10-04). The\n registry is the single source of every component in every format: `.tsx`, `.rs`, `.astro`\n (and `.mz`, the Mzizi language, as it lands). A component's `.astro` sits beside its `.tsx`\n and `.rs` (`components/registry/n<N>-*/<name>.astro`), pure Astro with no React, Svelte or\n Vue under it and no client JavaScript unless its contract allows one enhancement script.\n Install with `mzizi add <name> --target astro` (inferred in an Astro project, even one with\n React islands): it reads `api.mzizi.dev/v1/astro/<name>` and writes the component,\n its flat-imported siblings (`./button.astro`, `./ui-utils`, `./server-table`) and any brand\n asset into `src/components/mzizi/`. A component with no `.astro` is refused by name, never\n replaced by its `.tsx`. `mzizi_get_component` returns the Astro block too.\n\nThe Mzizi Dashboard Standard's app components (`app-*`, plus the primitives `button`, `badge`,\n`card`, `alert`, `input`, `label`, `skeleton`), the Mzizi Discover Standard's components\n(`discover-*`, including the detail pattern) and the marketing-site components (`site-*`) all\nhave an `.astro` AND a `.tsx` in the registry (the `ui/` family's Astro ports keep the\nregistry's own `.tsx`, which share only what their contract's `identity` says), each held to\nits contract (\"The Dashboard and Discover Standards' contracts\" below). `@bundu/ui`\n(`@bundu/ui/app/*`, `@bundu/ui/discover/*`) is BUILT from these files at a pinned registry\ncommit, and its CI fails on drift; a Next.js app adopts the same design through the registry's\n`.tsx` (`mzizi add app-data-table --target tsx` or\n`npx shadcn add` of `api.mzizi.dev/v1/ui/<name>`). Use them as they are: do not\nfork, restyle or wrap the shell or a component; fix it upstream in the registry (contract,\n`.astro` and `.tsx` together), in the same change.\n\n## Starting an app\n\nFor a new Mzizi-ecosystem app, or any app that consumes the design system. Pick the path\nfirst (above); these steps are the Astro one, with the pure-Rust differences noted.\n\n### 1. Create the project (Astro)\n\n```bash\npnpm create astro@latest my-app -- --template minimal --typescript strict\ncd my-app\npnpm astro add tailwind cloudflare\n```\n\nAstro renders HTML by default, which is what makes a new app indexable by N11 and paintable on\na low-end device without opting in to either. For a pure-Rust app, start from\n`cargo new my-app` and a Dioxus 0.7 app instead, and skip to step 3.\n\n### 2. Wire the tokens\n\nCopy the generated stylesheet rather than retyping hexes: `skills/mzizi-design/tokens/palette.css`\nin `@nyuchi/mzizi-skills` (all 21 families, light and dark, generated from\n`palette.canonical.json` at the package root). Import it from your global stylesheet (in Astro,\n`src/styles/global.css`) and register the properties in the Tailwind 4 `@theme` block so\nutilities generate. In Rust the same palette is the `mzizi-tokens` crate, always on in\n`mzizi-roots`. How the 21 families are meant to be used, the radius scale and the 48px floor\nare in the `mzizi-design` skill.\n\nPick the app's accent through `--brand-accent` rather than hardcoding a mineral hex: nyuchi =\ngold, mukoko = tanzanite, and a Mzizi surface = hematite (a Heritage tone, not a mineral; see the\nconstellation in `mzizi-design`).\n\n### 3. Add the Roots components\n\n```bash\ncargo add mzizi-roots # tokens, primitives, brand components, app shell\n```\n\nFor Astro, a small Rust crate (a build step, or a `workers-rs` Worker for request-time\nrendering) renders each component to HTML with `dioxus-ssr`, and Astro places the fragment.\nThe fragment carries the same Tailwind classes as the React build, so the stylesheet from step 2\nstyles it, and it ships no JavaScript. Interactive islands (Roots compiled to wasm and wrapped\nas custom elements) are proposed and not packaged yet. \"Using a UI component\" above has a\ncompiling example.\n\nNever copy component source between apps. Depend on the crate, so every app inherits fixes.\n\n### 4. Give the app's AI assistants the doctrine\n\nInstall the published skills bundle so any assistant working in the repo has the design system,\nthe language and the components on hand:\n\n```bash\nnpm install -D @nyuchi/mzizi-skills\nnpx skills experimental_sync # links the bundled skills into .agents/skills/\n```\n\n`npx skills add @nyuchi/mzizi-skills` does **not** work: the `skills` CLI reads that argument\nas a GitHub repository and fails to clone it. `experimental_sync` is the CLI's route for skills\nthat ship in `node_modules`, and it is marked experimental. The same skills are served with no\ninstall at all by the MCP's `mzizi_get_skills`.\n\nThe public Claude Code plugin installs these skills and the MCP in one step:\n\n```\n/plugin marketplace add mzizi-dev/mzizi-registry\n/plugin install mzizi@mzizi\n```\n\nTo wire the MCP directly instead, add it to `.mcp.json` or your client config. It is free with\nno account; only the Fundi tools need sign-in:\n\n```json\n{\n \"mcpServers\": {\n \"mzizi\": { \"type\": \"http\", \"url\": \"https://mcp.mzizi.dev/mcp\" }\n }\n}\n```\n\n### 5. Let fundi read the project\n\n`@nyuchi/mzizi-cli` ships the **fundi** agent, which reads the project off disk and plans the\nwiring against the registry:\n\n```bash\npnpm add -D @nyuchi/mzizi-cli\npnpm exec fundi explore # offline — snapshot the project\npnpm exec fundi plan \"wire up Mzizi tokens\"\n```\n\nRun the bins from the installed package (`pnpm exec`, or `npx` inside the project). A bare\n`npx fundi` outside it fetches an unrelated npm package called `fundi`. `explore` runs without\ncredentials; `plan` and `chat` need `ANTHROPIC_API_KEY`. A plan is\ninert until you apply it, so read it before letting it write.\n\n### 6. Verify\n\n```bash\npnpm dev\n```\n\nConfirm the tokens resolve in light and dark, a rendered Roots fragment picks up the styles,\nand the page is readable with JavaScript off.\n\n### 7. An existing React app\n\nIf the app is already React, the React build installs with the CLI or shadcn, by absolute URL:\n\n```bash\npnpm add -D @nyuchi/mzizi-cli && pnpm exec mzizi add button # infers the tsx target\nnpx shadcn@latest add https://api.mzizi.dev/v1/ui/mzizi-theme-provider\n```\n\nPoint `components.json` `registries.mzizi.url` at `https://api.mzizi.dev/v1/ui`. A bare name\nresolves against shadcn's own registry and installs a different component. Do not start a new\napp this way.\n\n## Server components\n\nA Roots server component is a **sans-IO core**: typed request in, typed response out, every\nexternal facility (a store, a clock, an outbound fetch, a secret) behind a trait. The host owns\nI/O and credentials. `n10-documentation/mzizi-docs-api.rs` is the model: it parses a path into a\ntyped `Route`, asks a `DocsStore` trait for data and returns an `ApiResponse`. `mzizi-assurance`,\n`mzizi-fundi` and `mzizi-discovery` have no dependencies at all. The RFC records a passing\n`wasm32-unknown-unknown` `cargo check` for those three and `mzizi-docs`; CI does not run a\nwasm32 job yet.\n\nA host adapter mounts the core: a `workers-rs` adapter for Cloudflare Workers, an axum adapter\nfor containers. Neither adapter crate exists yet. Until then the host writes the few lines that\nturn its request into the core's request (see the `mzizi-backend` skill). Dioxus\n`#[server]` functions are one more possible adapter, not the building block.\n\n## Contracts\n\nEvery Roots component carries a contract, in two parts:\n\n1. **The registry contract:** one registry name, description, dependencies and variants shared\n by both builds, and the same `data-slot`, `data-portal` and tokens. Each crate's\n `tests/contract.rs` reads the `.tsx` sibling and asserts that the slot, the portal and every\n variant value the Rust emits appear there too. A deliberate divergence, such as a touch\n target raised to the 48px floor, is asserted from both sides, so the test fails when the\n React build is fixed.\n2. **Checkable clauses** in the Mzizi language's `contract … end` grammar (RFC-0006; the\n `mzizi-language` skill), exported as `pub const CONTRACT: &str`. From\n `n3-brand/mzizi-hero-stat.rs`:\n\n ```rust\n pub const CONTRACT: &str = r#\"contract\n slot is \"mzizi-hero-stat\"\n role is \"region\"\n label not_empty\n button \"Share\" min_height 48\n end\"#;\n ```\n\n `mzizi-brand`'s contract suite renders each component with `dioxus-ssr` in named states and\n evaluates every clause against the markup it emits. **A clause it cannot evaluate fails.**\n `mzizi_roots::brand::CONTRACTS` lists every brand component with its contract.\n\nToday the twelve `mzizi-brand` components export `CONTRACT`. The other crates check the\nregistry contract against the `.tsx`, and their `CONTRACT` clauses arrive batch by batch.\nServer-component clauses (status per route, CORS and cache headers, no credential in the core)\nwait on the language's contracts-everywhere RFC (RFC-0010). Until then their tests assert those\nproperties against an in-memory implementation of the traits. The language's own `service`\ncontracts (`example` and `ensure`, RFC-0011) apply to a Mzizi `service`, not to these Rust\ncrates. No component lowers to Rust yet (only a Mzizi `service` does, to a local axum\npackage), so `mz contract` does not read a Roots `CONTRACT`.\n\n### The Dashboard and Discover Standards' contracts\n\nThe registry's\n[`contracts/`](https://github.com/mzizi-dev/mzizi-registry/tree/main/contracts) holds a\nmachine-readable, versioned contract for every component of `@bundu/ui` (built in\n`mzizi-dev/packages-npm`), in two families of one format:\n\n- **`app/`, the Mzizi Dashboard Standard** (since registry `a298a4e`, 2026-10-04,\n mzizi-registry #404): the one dashboard design for every product in the Bundu ecosystem,\n with each brand's mineral as an overlay (docs.mzizi.dev/patterns/dashboard-standard). Its 31\n contracts are the server-rendered app components (`@bundu/ui/app/*.astro`): the shell, the\n page and data patterns, and seven primitives.\n- **`discover/`, the Mzizi Discover Standard** (since registry `0bea10c`, 2026-10-04,\n mzizi-registry #415, tracking issue #413): one design for every public discover and browse\n page in the Mukoko family (docs.mzizi.dev/patterns/discover-standard). Its 11 contracts are\n the Discover components (`@bundu/ui/discover/*.astro`). \"The Discover Standard\" below says\n what is different.\n\n| File | What it is |\n| ------------------------------------------------- | ---------------------------------------------------------------------------------- |\n| `contracts/schema/component-contract.schema.json` | the format, one for both families |\n| `contracts/app/<name>.contract.json` | one Dashboard Standard component's contract |\n| `contracts/discover/<name>.contract.json` | one Discover Standard component's contract |\n| `contracts/site/<name>.contract.json` | one marketing-site component's contract |\n| `contracts/ui/<name>.contract.json` | one Astro port of a registry primitive (native select, segmented control, …) |\n| `contracts/index.json` | all 59 contracts with their version and their `.astro`, `.tsx` and `.rs` items |\n| `contracts/README.md` | the coverage tables, the recorded gaps, the shared rules, how to change a contract |\n\n**Read the contract before you build or change a `@bundu/ui` component, in any language.** It is\nthe specification: `props` and `slots` (types, required, defaults, closed `values`; the\n`.astro` file's props and slots must be exactly these), `behaviour`, `states` (named fixtures,\n`default` required; every runner renders every state), `accessibility` (roles, keyboard,\nfocus, ARIA, WCAG 2.2 criteria), `density`, `theming`, `noJs`, `responsive`, the `contract`\nblock and `checks` (selector assertions on the rendered markup). `implementations` names the\nAstro export and the `.tsx` and `.rs` siblings, with what they share (`identity`: `slot`,\n`slot+role` or `slot+variants`) and their `divergences`; items under `related` look similar\nand have a different contract, so they are not implementations. `implementations.astro`\ngives the package, the export and `since`, which is `@bundu/ui` **0.3.0 for every contract in\nboth families** (mzizi-registry #419): the next `@bundu/ui` release, one minor above 0.2.0 on\nnpm. `gaps` records where the current build falls short.\n\nFour fields are requirements. packages-npm's contract test evaluates the first three on the\nrendered Astro build; the responsive rules are prose. The values below are the Dashboard\nStandard's; the Discover Standard's differ (below):\n\n- **No-JS fallback.** `noJs.script` is `none` (no script) or `enhancement` (one script, and\n everything in `noJs.without` works when it does not run). Only AppShell and CommandPalette\n are `enhancement`; every other app component ships no script.\n- **Density.** Each row gives a part's height in px for a fine pointer and a coarse one\n (touch), read from the rendered classes (`h-N`, `min-h-N`, `size-N` = N×4;\n `pointer-coarse:` for touch). Dashboards are dense on a mouse and 44–48px on touch: the app\n Button is 36px fine (32 `sm`, 40 `lg`) and 48px coarse (44 `sm`), with the small radius,\n and nav rows are 32 / 44. That is the contracts' rule for `@bundu/ui/app`, not the registry\n primitives' 48px floor. A control under 44px on touch is listed in `gaps`, not hidden.\n- **Theming.** A brand overlay changes colour only: `--primary`, `--ring`, AppShell's\n `--app-accent` and the brand mark. Layout never varies by brand. A mineral may be named only\n as one of the component's declared `statusColours`; any other mineral class, or any colour\n value, fails (the `mzizi-design` skill).\n- **Responsive.** Each contract's rules from 320px up.\n\nIn both families the tests also fail **any inline `style` attribute** on a rendered element, so\na page's Content-Security-Policy can keep `style-src 'self'` with no\n`style-src-attr 'unsafe-inline'`: custom properties go through classes or data attributes\n(AppShell's `data-accent`), sizes through classes or SVG geometry (BarChart). JSON-LD\n(`type=\"application/ld+json\"`) is data and does not count as a script.\n\n**How they relate to `CONTRACT` and the language.** The `contract` field is a\n`contract … end` block in the same clause grammar as a Roots `pub const CONTRACT` (RFC-0006\nand RFC-0010), restricted to what applies to rendered markup: subjects `slot`, `role`,\n`label`, `class`, `portal`, `<element> \"<text>\"`, `uses <data-slot>` and `when <state> …`;\npredicates `is`, `contains`, `not_empty`, `in`, `uses \"--token\"`, `min_height` and `shows`. As\nin the language, **a clause, check or density row a runner cannot evaluate fails** (FM-12).\nThe registry's own suites evaluate them: `__tests__/astro/contracts.test.ts` renders every\n`.astro` in every state (Astro's container, no renderer), `__tests__/contracts/\ntsx-contracts.test.tsx` renders every `contract`-identity `.tsx` with the same runner\n(`contracts/runner.ts`), and `__tests__/contracts/contracts.test.tsx` validates every contract\nagainst the schema and the grammar and checks each identity-only sibling. packages-npm's\n`src/app/contracts.test.ts` runs the same contracts again on the built package. A crate's own `CONTRACT`\nand `tests/contract.rs` still run as before.\n\nEvery contract has an `.astro` (`implementations.astro.registry`). The seven app primitives'\n`.tsx` are the registry's own `button`, `badge`, `card`, `alert`, `input`, `label` and\n`skeleton`, which share only their identity (the contract lists the divergences), as do the\n`ui/` family's; every other `.tsx` (`app-*`, `discover-*`, `site-*`) has\n`identity: \"contract\"`, held to the whole contract exactly as the Astro build is. A `.rs`\nexists in `mzizi-ui` for `button`, `badge`, `card`, `input`, `label` and `safe-area-frame`;\nmzizi-registry #401 tracks Rust ports of the rest, done when each passes the contract.\n\n### The Discover Standard\n\nThe 11 Discover Standard list-page contracts in `discover/` and the node each declares (the detail pattern below adds five more). DiscoverShell's `kind` is `shell`;\nthe other ten are `pattern`s.\n\n| Contract | Component | Node | What it is |\n| --------------------------- | --------------- | ---- | --------------------------------------------------------------------------------------------- |\n| `discover/discover-shell` | DiscoverShell | N7 | the frame: skip link, mineral strip, header (brand, \"Main\" nav, one action), `<main>`, footer |\n| `discover/discover-meta` | DiscoverMeta | N6 | the `<head>` tags: title, description, robots, canonical, Open Graph, the X card, JSON-LD |\n| `discover/discover-hero` | DiscoverHero | N6 | breadcrumbs, eyebrow, the page's one `<h1>`, lead, the search box and actions |\n| `discover/discover-search` | DiscoverSearch | N6 | the one search box: a `role=\"search\"` GET form, so every search is a URL |\n| `discover/category-chips` | CategoryChips | N6 | the category links as a named navigation landmark; chips wrap |\n| `discover/category-chip` | CategoryChip | N6 | one category link, with an optional count; a link, never a button |\n| `discover/discover-section` | DiscoverSection | N6 | one band: eyebrow, heading, description and \"See all\" over its content |\n| `discover/result-grid` | ResultGrid | N6 | a polite status line, the cards, the empty state and \"load more\"; `grid`, `list` or `rail` |\n| `discover/discover-card` | DiscoverCard | N6 | one card for every page, variants `article`, `event`, `circle`, `place` |\n| `discover/load-more` | LoadMore | N6 | cursor pagination: one `rel=\"next\"` link to `?cursor=…` |\n| `discover/open-in-app` | OpenInApp | N6 | \"Open in Mukoko\": an https link into the super-app, with one line saying what happens |\n\nSame format, schema, clause grammar and runners as the Dashboard Standard. What the Discover\ncontracts state differently:\n\n- **What it is for.** Public discover and browse pages in the Mukoko family (circles, news,\n events, weather, and the super-app on the web), not admin or operator dashboards. It reuses\n the Dashboard Standard where they overlap: ResultGrid's `empty` slot takes `app/empty-state`\n or `app/state-message`, and `related` names `app/filter-bar` and `app/pagination` as similar\n components with a different contract.\n- **No script at all.** Every Discover contract's `noJs.script` is `none`; there is no\n `enhancement` like AppShell's or CommandPalette's. Views are URLs, not client state: search\n is a GET form (`?q=`, with no `cursor`, so a new search starts at the first page), a category\n is a link, and paging is LoadMore's `rel=\"next\"` link to `?cursor=`.\n- **The same size on every pointer.** In every Discover density row `fine` equals `coarse`:\n 48px for the search field and its button, \"See all\", LoadMore, OpenInApp and DiscoverCard's\n app link; 44px for chips, the navigation links and the home link. Discover pages are not\n dense on a mouse, unlike the Dashboard Standard (most controls 32–40px on a fine pointer).\n- **Theming.** The brand-overlay rule is the same (colour only, no colour values, a mineral\n only as a declared status colour), but there is no AppShell `--app-accent`. A brand shows in\n DiscoverShell's `brand` slot (its mark) and in whatever primary action its `action` slot\n holds; the frame, header, navigation and footer are identical in every brand. `--primary` reaches only\n DiscoverHero's wash (`gradient-showcase`), DiscoverCard's monogram and `brand` badge tone,\n and OpenInApp's `primary` variant. Chips, sections and LoadMore are neutral in every brand,\n and DiscoverSearch follows the brand only in its focus ring (`--ring`). Only DiscoverCard\n declares status colours (`cobalt`, `malachite`, `gold`, `terracotta`), for its badge tones.\n- **Server-filled shells.** Every Discover component also works in an Astro page built once\n with `{{placeholders}}` and filled by a server, as circles.mukoko.com's Rust Worker does:\n text props are plain strings, text that fills to `\"\"` hides itself, and state switches are\n attributes styled by classes (ResultGrid, DiscoverSection and LoadMore `state`, DiscoverCard\n `badgeTone`, CategoryChip `current`). CategoryChip, DiscoverCard, DiscoverHero, LoadMore and\n ResultGrid have a `template` state with placeholders, rendered like every other state.\n DiscoverCard and CategoryChip render their own `<li>`, so they repeat as fragments.\n- **One card.** DiscoverCard's `variant` changes emphasis, never structure. Its title is the\n one primary link, stretched over the card; other actions stay separately focusable.\n- **OpenInApp's `href` is https** (a universal link or a server redirect), never a bare\n `mukoko://` link, which does nothing without the app.\n- **No siblings and no gaps yet.** No `.tsx` or `.rs` implements a Discover contract, and none\n records `gaps`. A React or Rust port is done when it passes the same contract.\n\n**Upstream first** (registry `CONTRIBUTING.md`, from #415, for both families): a component an\napp needs that Mzizi does not have, or a change to one it has, goes upstream immediately: its\ncontract in the registry, its build in `@bundu/ui` and its page on docs.mzizi.dev. An app keeps\na local copy only while that upstream pull request is open, marked at the top of the file with\n`TODO(mzizi): <upstream PR URL>`.\n\n### Changing a contract\n\n1. Edit `contracts/<family>/<name>.contract.json` in the registry and bump its `version`:\n major when a clause, prop, slot or state is removed or narrowed, minor when one is added,\n patch for prose. A new family also needs its directory in `FAMILIES` in\n `__tests__/contracts/contracts.test.tsx` and in packages-npm's `scripts/contract-paths.mjs`.\n2. Update `contracts/index.json` and the README's coverage table for that family. The test\n fails if either disagrees.\n3. Run `pnpm test` in the registry, then `pnpm contracts:fetch <branch>` and `pnpm test` in\n packages-npm. The Astro build must pass the new contract before either side merges. Never\n edit the copy in packages-npm.\n\n**The detail pattern** (mzizi-registry #429; docs.mzizi.dev/patterns/discover-detail): the\npage for one item reached from a discover page uses five more `discover/` contracts, each an\n`.astro` and a `.tsx`: `detail-hero` (the page's only `<h1>`, when/where, lead, image or media\nslot), `breadcrumb` (named `<nav>`, `aria-current=\"page\"`, BreadcrumbList JSON-LD), `meta-list`\n(a `<dl>` of facts), `detail-actions` (Open in Mukoko first, then secondary and share links; no\nscript) and `related-rail` (DiscoverCards; a scroll-snap row on phones, a grid from 48rem;\nhidden when empty).\n\n**Open in Mukoko** is one canonical https universal link, `mukoko.com/open/<service>/<id>`\n(`openInMukokoUrl` in `discover-open-link.ts`; `@bundu/ui/discover/open`). `open-in-app` 1.1.0\nand `detail-actions` take `service` (`news`, `events`, `weather`, `circles`) and `id`; `href` is\nonly an override. The apps claim `mukoko.com/open/*` as universal and app links; on the web a\nresolver (mukoko-dev/super-app-web `/open/*`) sends the reader to the service's page. Never a\nbare `mukoko://` link.\n\n## Reuse before build\n\n**The simplest change is the one you don't write.** Reuse before you build, compose before you\nimplement, and let one source own each decision. Use this before adding a component, a style or\na page, and during any refactor.\n\n1. **Reuse before build.** Search the registry first: `mzizi_search` or\n `mzizi_list_components` over the MCP (`https://mcp.mzizi.dev/mcp`, free), or\n `GET https://api.mzizi.dev/v1/ui`. Where a Rust implementation exists, depend on its crate\n (`cargo add mzizi-roots`). For the React build, install with\n `mzizi add <name>` or `npx shadcn@latest add https://api.mzizi.dev/v1/ui/<name>`. Never\n reimplement a primitive that already exists.\n2. **Styles are tokens.** Never hardcode a colour, radius, spacing, or motion value —\n consume `var(--…)` (or the `mzizi-tokens` consts in Rust). N1 is the only layer allowed to\n define values, so a rebrand is one edit. A new colour goes in the registry's\n `lib/tokens/palette.source.ts`, and `pnpm tokens:sync` regenerates `styles/globals.css`, the Rust\n tokens and the other emitters from it.\n3. **Compose, don't implement.** A page (N6) is a composition of N2 primitives and N3 brand\n components — never inline a `<button>`, a card, or an SVG. Pull spinners, skeletons, and\n badges from N2.\n4. **Variants over copies.** Collapse near-duplicate components into one variant table: an\n enum with a `match` in Rust, an `enum` table in Mzizi source, one `cva()` in the React\n build. Express differences as `variant` / `size` props, not new files.\n5. **One composition path.** In the React build, compose class names through `cn()` and\n extract shared logic into `lib/` or a hook. In Rust, a pure function beside the component.\n No logic duplicated across files.\n6. **Right altitude.** Keep each component self-contained, importing only from the layer\n below on its strand. A primitive that reaches up into page code can't be installed on its\n own.\n7. **Delete first.** Prefer removing over adding — dead code, unused props, redundant\n wrappers, commented-out blocks. Less code is less to break, read, and keep true.\n8. **Live data, not copies.** Read counts, tokens and doctrine from their source\n (`https://api.mzizi.dev/v1/stats`, `palette.canonical.json`, `mzizi_get_tokens`,\n `mzizi_get_doctrine`). A baked-in number or duplicated token guarantees drift.\n\n### Checklist\n\n- [ ] Searched the registry before building, and used the Rust build where one exists?\n- [ ] All styles are tokens (`var(--…)`), zero hardcoded values outside N1?\n- [ ] Page is pure composition of N2/N3, no inlined primitives?\n- [ ] Variation expressed as one variant table, not near-duplicate files?\n- [ ] One composition path, shared logic extracted once?\n- [ ] Each file self-contained and installable, importing only downward?\n- [ ] Deleted more than you added where you could?\n- [ ] Counts, tokens, and doctrine read live, not hardcoded?\n\n## Contributing a component\n\n**A component is a real file.** `components/registry/n<N>-<label>/<name>.rs` (Rust, compiled by\nthe node's crate in `mzizi-rs/`) and, for the React build, `<name>.tsx` beside it, which\n`pnpm typecheck` (`tsc --noEmit`) and `pnpm lint` check. An error in either fails CI. The\nregistry has no app to build: CI's `Rust` job runs `cargo fmt`, `check`, `clippy` and the\ncontract tests, and its `Build` job runs `pnpm build`, which runs every generator in write mode\nand fails if that changes a committed file.\n\n| File | What it is |\n| --------------------------------------------- | ----------------------------------------------------------------- |\n| `components/registry/n<N>-<label>/<name>.rs` | the Roots component (Dioxus UI, or a sans-IO server core) |\n| `components/registry/n<N>-<label>/<name>.tsx` | the React build of the same contract |\n| `registry.json` | the manifest `/v1/ui/<name>` and `npx shadcn add` resolve against |\n\n`registry.json` carries the install contract (description, dependencies,\n`registryDependencies`, target paths) and **never the source**. The node is derived from the\ndirectory the component lives in, so it cannot disagree with where the file is.\n\n**There is no database.** The registry's files are its data layer (mzizi-registry PR #368), and\na test fails the build if a Supabase import or a `SUPABASE_*` read comes back. Nothing about a\ncomponent, including its source, status or history, is written to a table. If you find\ninstructions anywhere telling you to `INSERT` a component or read a `component_documents` row,\nthey are stale.\n\n### Step 1 — Decide the node\n\nThe architecture is the **Mzizi DNA double helix**: eight nodes (N1–N8) on the engineering\nbackbone, four rungs (N9–N12) that bridge both backbones, and six strands. The N-numbers are\n**labels, not a sequence**.\n\nThe node decides the **directory**, the directory decides the **crate** that compiles the Rust\n(`mzizi-rs/crate-for-node.json`), and the directory is what the build reads. Choosing the node\nand choosing where the file goes are the same decision.\n\n| Node | Directory | Crate | What belongs there |\n| ----------------- | -------------------- | ----------------- | --------------------------------------------------------------------------- |\n| N1 tokens | `n1-tokens/` | `mzizi-tokens` | A token value or emitter (colour, spacing, motion, radius), not a component |\n| N2 primitive | `n2-primitives/` | `mzizi-ui` | Generic UI: button, card, input, dialog, toolbar, bento-grid |\n| N3 brand | `n3-brand/` | `mzizi-brand` | A branded composition with the mineral palette |\n| N4 safety | `n4-safety/` | none yet | A conditional-rendering gate: permission, geo, rate limit |\n| N5 resilience | `n5-resilience/` | none yet | Error boundary, skeleton, offline banner, fallback chain |\n| N6 pages | `n6-pages/` | none yet | Full-screen layout composition |\n| N7 shell | `n7-shell/` | `mzizi-shell` | App container: navigation, routing, lifecycle |\n| N8 assurance | `n8-assurance/` | `mzizi-assurance` | Instrumentation, a11y audit, RTL conformity, probes |\n| N9 fundi | `n9-fundi/` | `mzizi-fundi` | Automated healing (a rung) |\n| N10 documentation | `n10-documentation/` | `mzizi-docs` | Docs, AI instructions, docs infrastructure (a rung) |\n| N11 discovery | `n11-discovery/` | `mzizi-discovery` | SEO / AIO surfaces: if the machine can't see it, it doesn't exist (a rung) |\n| N12 skills | none in the registry | none | Agent skills (a rung), authored in `mzizi-dev/agent-tools/mzizi-skills/` |\n\nA node with no crate yet (N4, N5, N6) takes a `.tsx` today. Adding its first `.rs` means adding\nits crate and its `crate-for-node.json` entry in the same pull request; `pnpm rust:generate`\nrefuses Rust that no crate claims.\n\nIf you are unsure where something belongs, ask the MCP (`https://mcp.mzizi.dev/mcp`, free, no\naccount): `mzizi_list_components` with `node: N` shows what already lives in a node, and\n`mzizi_get_architecture` returns the node, rung and strand model with live counts. Never\nhardcode counts.\n\n### Step 2 — Write the file\n\nCreate it in the directory the node names:\n\n```\ncomponents/registry/n3-brand/mzizi-your-component.rs # the Roots component\ncomponents/registry/n3-brand/mzizi-your-component.tsx # the React build, same contract\n```\n\nThe Rust file carries `pub const CONTRACT: &str` and follows the rules in \"Using a UI component\" above: variants as enums, tokens by reference, browser facilities as props, icons as slots,\nno helpers shared between components. `.ts` is for a non-rendering module.\n\nThat is the authoring step. There is no row to insert and no registration to perform before\nwriting code: the file _is_ the component, and `cargo test` (for the `.rs`), `pnpm typecheck`\nand `pnpm lint` (for the `.tsx`) and `pnpm build` (every derived artifact regenerated, with no\ndiff) are what tell you it is real.\n\nNaming: N3 brand components take a `mzizi-` prefix. (Components formerly named `nyuchi-*` are\n`mzizi-*`, and the old names redirect.) Everything else is the bare name, matching what a\nconsumer will type in `mzizi add` or `npx shadcn add`.\n\n### Wiring the Rust into its crate\n\n`pnpm rust:generate` writes the committed copy to `mzizi-rs/crates/<crate>/src/generated/`. Add\nthe `#[path = \"generated/<name>.rs\"] pub mod <name_snake>;` line and the `pub use` to the\ncrate's `src/lib.rs` by hand, and add the component to the crate's `tests/contract.rs`. Edit\nthe registry file, never the copy. The workspace lints forbid `unsafe_code` and warn on\n`missing_docs`; clippy turns the warning into a failure. Cap parallel builds on a small machine\n(`CARGO_BUILD_JOBS=2`).\n\nA release is a version bump in `mzizi-rs/Cargo.toml`. On merge to `main`,\n`.github/workflows/publish-crates.yml` re-runs the Rust gate and publishes each crate in\ndependency order, skipping any version already on crates.io. A published crate name is\npermanent. Say in the PR body that components changed, so the site, docs and skills are\nupdated to match.\n\n### Step 3 — Declare it in `registry.json`\n\nOne item, carrying the install contract. `meta` is the part you author; four other fields are\n**derived from it** and must not be hand-written.\n\n```jsonc\n{\n \"name\": \"your-component-name\",\n \"type\": \"registry:ui\",\n \"description\": \"Short description of what it does.\",\n \"dependencies\": [],\n \"registryDependencies\": [],\n \"files\": [\n {\n \"path\": \"components/registry/n2-primitives/your-component-name.tsx\",\n \"target\": \"components/ui/your-component-name.tsx\",\n \"type\": \"registry:ui\"\n }\n ],\n \"meta\": {\n \"collection\": \"primitives\",\n \"owner\": \"mzizi\",\n \"useCases\": [\"Use case 1\", \"Use case 2\"],\n \"variants\": [\"default\", \"sm\", \"lg\"],\n \"sizes\": [\"default\", \"sm\", \"lg\"],\n \"features\": [\"cn() for className composition\", \"data-slot attribute\"],\n \"a11y\": [\"ARIA attributes\", \"Keyboard accessible\"],\n \"hasDemo\": true\n }\n}\n```\n\n`files[]` lists the `.tsx`: it is the shadcn install contract for the React build. The `.rs`\nbeside it is not listed; the node's crate compiles it, and `/v1/rs/<name>` serves it with the\ncrate's name.\n\n**Do not write `title`, `categories`, `docs` or `author`.** `pnpm registry:metadata` derives all\nfour from `meta` and the repo. `docs` in particular is what the shadcn CLI prints _after_\ninstalling, so it is generated from your `useCases` / `variants` / `sizes` / `features` / `a11y`\nrather than duplicated by hand — and an item with no `meta` deliberately gets no `docs`, because\na stub reads as documentation and says nothing.\n\nThen run the generators and let the gates check you:\n\n```bash\npnpm registry:paths # fill file paths from what is on disk\npnpm registry:metadata # derive title, categories, docs, author\npnpm registry:normalize # canonical ordering\npnpm registry:validate # every item resolves on disk; every dependency addressable\npnpm registry:verify # the CI drift gate\npnpm typecheck && pnpm lint && pnpm build # build: every generator; commit what it writes\n```\n\nFollow the criteria for the chosen node.\n\n#### Every node: the Rust file\n\n- `pub const CONTRACT: &str` with the clauses the rendered markup must meet (slot, role, touch\n floors, variant tables), evaluated by the crate's `tests/contract.rs` on `dioxus-ssr` output\n- The same `data-slot`, `data-portal` and variant values as the `.tsx`, asserted by the same\n suite; a deliberate divergence is asserted from both sides\n- `prefers_reduced_motion: bool` as a prop where the `.tsx` reads the harness's motion\n- Icons as `Element` slots, not an icon crate\n- Doc comments on every public item (`missing_docs` is a warning, and clippy runs with\n `-D warnings`); no `unsafe`\n\n#### N2 primitive checklist\n\n- No `useMziziHarness` import in the `.tsx`\n- `data-slot` attribute on the root element\n- `data-portal` attribute pointing at `https://mzizi.dev/components/<name>`\n- `cn()` for className composition in the `.tsx`\n- No raw Tailwind colours: use semantic tokens (`bg-primary`, `text-foreground`, …)\n- Icons from `@/lib/icons` in the `.tsx`, never `lucide-react` directly\n- **Touch targets never go below 48px.** Buttons and inputs are `h-14` (56px) default and\n `h-12` (48px) small, as `button.tsx`, `button.rs` and `primitives/button.mz` all state, and\n `/v1/brand` `.componentSpecs` serves (`minTouchTarget: 48`). A smaller visual box, such as a\n badge, is not a touch target; if it becomes one, give it `min-h-[48px]`.\n- Pill-shaped categories (button, input, avatar, badge, toggle) use `rounded-full`\n\n#### N3 brand checklist\n\n- `mzizi-` prefix on the name\n- `useMziziHarness(\"<name>\")` in the `.tsx` for the motion preference (and `log` or\n `LiveRegion` where the component uses them); `prefers_reduced_motion` as a prop in the `.rs`\n- The entrance animation off when motion is reduced\n- An ARIA role or `aria-label`, and a region that is named in every state, the loading\n skeleton included\n- `data-slot` and `data-portal` attributes, in every state\n- `focus-visible` ring on interactive elements, and interactive elements at the 48px floor\n- I18N via `Intl` formatters for dates, numbers and currency\n- Semantic tokens for status-bearing colours\n\n#### N6 page checklist\n\n- Pure composition: no inline buttons, cards or SVGs\n- Accept children/slots for content\n- Semantic CSS vars only (`bg-card`, `text-foreground`, `bg-primary`)\n- A loading-state prop\n- `role=\"main\"` and an `aria-label`\n\n#### N8 assurance checklist\n\n- A sans-IO Rust core (typed input, typed result, every facility behind a trait) in\n `mzizi-assurance`, with the `.ts` module as the React build\n- A banner comment identifying the node and purpose\n- A configurable rules array\n- Optional `onViolation` / `onComplete` callbacks\n\n### Step 4 — Docs and demos\n\nThere is nothing extra to write. Docs come from the `meta` you already authored in Step 3\n(`useCases`, `variants`, `sizes`, `features`, `a11y`), and `pnpm registry:metadata` turns them\ninto the `docs` string the CLI prints after install. Set `meta.hasDemo` when a demo exists.\n\nIf a prop table is expected, `pnpm props:extract` derives it from the component's own types and\n`pnpm props:verify` is the gate. Props are read from the code, never restated beside it.\n\n### Step 5 — Versions and history\n\nThere is no status row and no database. A component's version history is the registry's\nchangelog (`content/changelog/`, generated, checked by `pnpm changelog:generate:check`) and the\nrename map. `mzizi_get_component` with `include: [\"versions\"]` returns it. Read what a component\nis through the MCP or `https://api.mzizi.dev/v1/ui/<name>` rather than assuming.\n\n### Step 6 — Validate accessibility\n\nIf your component introduces a new colour pair, validate it before shipping. The MCP tool is\ncomputed locally and needs no account:\n\n```\nmzizi_check_accessibility(foreground: \"#FFFFFF\", background: \"#0047AB\")\n```\n\nIt returns the WCAG 2.1 contrast ratio with AA/AAA verdicts and whether the pair clears the\nMzizi floor (APCA and AAA).\n\n### Step 7 — Before you open the PR\n\n`pnpm check` runs every registry gate CI runs, in order, including `registry:validate`,\n`registry:verify`, `tokens:verify`, `changelog:generate:check`, `rust:generate:check`, the\ntests and the build. Then the Rust workspace gate:\n\n```bash\npnpm check\ncargo fmt --manifest-path mzizi-rs/Cargo.toml --all -- --check\ncargo clippy --manifest-path mzizi-rs/Cargo.toml --workspace --all-targets -- -D warnings\ncargo test --manifest-path mzizi-rs/Cargo.toml --workspace\ncargo package --manifest-path mzizi-rs/Cargo.toml --workspace\n```\n\nIf you touched `files[]` or `meta`, run `pnpm registry:paths`, `pnpm registry:metadata` and\n`pnpm registry:normalize` first; their `--check` twins fail otherwise.\n\nA green run means the item resolves to real files, every dependency is addressable, the derived\nfields match `meta`, the Rust compiles, and its contract holds on rendered markup. Say in the PR\nbody that a component changed, so the site, the docs and the skills are updated to match.\n\n### What to avoid\n\n- **Never put component source in a database row, a JSON document, or any second place.** The\n file under `components/registry/` is the component; everything else is a representation of\n it, and a representation can drift from the code while still looking correct. That is not\n hypothetical here: a `source_code` column once served stale copies for 571 components.\n- Never edit `mzizi-rs/crates/*/src/generated/`. It is overwritten by `pnpm rust:generate`.\n- Never hand-write `title`, `categories`, `docs` or `author` in `registry.json`. They are\n derived, and a hand-written value is a second source that the next generator run silently\n overwrites.\n- Never hardcode hex colour values (except documented third-party brands like Ethereum,\n Google, EcoCash).\n- Never use `lucide-react` directly in a `.tsx`; go through `@/lib/icons`.\n- Never use `margin-left` / `padding-right` / `left`; use logical properties\n (`margin-inline-start`, …) for RTL support.\n- Never start new work in the React build alone. If a component has a Rust implementation,\n change the Rust and keep the `.tsx` in step.\n- Never bump a major version on your own initiative. That is a maintainers' call.\n\n## The React build\n\n`npx shadcn@latest add https://api.mzizi.dev/v1/ui/<name>` installs the `.tsx` for an existing\nReact app, for all 577 items. Always pass the absolute URL: a bare name resolves against\nshadcn's own registry and installs a different component. `mzizi add <name>` installs the React\nbuild too. Neither is where new work goes.\n\n## See also\n\n- `docs/roots/RFC-roots.md` in the registry: the inventory, the conversion batches and what\n the API and site still need for Rust-first serving (`/v1/roots` is proposed, not live).\n- The `mzizi-language` skill for the clause grammar and `mz contract`.\n- The `mzizi-design` skill for the palette, radius, type and the Ubuntu constraints.\n- The `mzizi-backend` skill for mounting a server core on Workers.",
20
24
  "source": "mzizi-dev/agent-tools/mzizi-skills/skills/mzizi-roots"
21
25
  },
22
26
  {
23
27
  "name": "mzizi-design",
24
- "description": "The Mzizi design system and brand: the canonical 21-family palette (seven African Minerals + seven Heritage tones + the Experimental Seven) with generated stylesheets (tokens/palette.css, tokens/minerals.css, tokens-update.css), the radius scale, Noto Sans/Serif + JetBrains Mono type, pill buttons and the 48px touch floor, status and surface tokens, the verified brand constellation, the ecosystem identity (copper), the five Ubuntu pillars + five principles, what belongs in the ecosystem, and the wordmarks. Replaces nyuchi-design and half of ecosystem-app-setup, and carries the tokens that were in mukoko-design.",
25
- "body_mdx": "# Mzizi design — the system and the ecosystem\n\nThis skill replaces three 0.7.0 skills: `nyuchi-design` (UI and tokens, now \"The design\nsystem\" below), the Ubuntu and ownership half of `ecosystem-app-setup` (now \"What belongs in\nthe ecosystem\"), and the tokens of `mukoko-design`, which moved here. The mukoko brand assets\n(the Swarm mark, logos, icons) are no longer in this package; they move to a Nyuchi-owned\nbundle.\n\n## Files this skill ships\n\nAll generated from `palette.canonical.json` at the package root by\n`scripts/generate-tokens.mjs`, and gated against it. Never hand-edit them; never retype a hex.\n\n| File | Contents |\n| ---------------------- | ---------------------------------------------------------------------------------------------------- |\n| `tokens/palette.css` | all 21 families, light + dark, as CSS custom properties |\n| `tokens/palette.json` | all 21 families, machine-readable |\n| `tokens/minerals.css` | the seven minerals only |\n| `tokens/minerals.json` | the seven minerals only, machine-readable |\n| `tokens-update.css` | all 21 families (light, dark, `.dark`, `prefers-color-scheme`) plus the radius scale, as one drop-in |\n\n## The ecosystem\n\n**Mzizi** is the parent identity at the ecosystem level — the framework, the design language, the\ncomponent registry and the open doctrine every brand builds on. Everything revenue-generating (the\nMzizi console, fundi, paid plans, billing) is Nyuchi's. Three pillars: **mukoko** (consumer super app), **nyuchi** (infrastructure &\nenterprise), and the **sister brands** (specialist verticals) — one identity, one design system, one\nopen data commons.\n\nDoctrine **v4.1.0**. **The repo is the source of truth, and there is no database.** Colour\ncomes from `palette.canonical.json` at the root of this package — 21 families, vendored from\n`mzizi-dev/mzizi-registry → lib/tokens/palette.source.ts` and gated against it. The ecosystem\nconstellation and the Ubuntu pillars and principles come from `mzizi-registry →\ncontent/doctrine/`, served at <https://api.mzizi.dev/v1/brand> (`.ecosystem`, `.philosophy`) and\nby the MCP as `mzizi_get_tokens` (`family: \"ecosystem\"`) and `mzizi_get_doctrine`. Two notes:\nprinciples were renamed in v4.x, so use the set below and not older Shona-named lists; and the\nregistry still names its ecosystem-layer entry `bundu` (URL **bundu.org**). That is registry\ndata, and it changes in mzizi-registry, not here.\n\nThe **five** pillars and **five** principles below are a count of Ubuntu doctrine, not of the\npalette, and they are correct — `/v1/brand` `.philosophy.ubuntuPillars` serves exactly these five.\nDo not \"fix\" them to seven to match the colour families.\n\n### The ecosystem-level identity\n\n- **Mineral:** `copper` — `#BF5A36` light / `#FF8A65` dark (`--color-copper`) · connection, foundation, stewardship\n- **Registry entry:** `bundu` in `/v1/brand` `.ecosystem` (meaning _wilderness_, Shona) · **URL:** <https://bundu.org>\n- **Voice:** visionary, grounded, inclusive\n- **Role:** the ecosystem — the layer that holds the whole, the commons, governance, philosophy\n- **Wordmark:** `Mzizi`, capitalised, Noto Serif 600. (The other wordmarks stay lowercase.)\n- **No standalone ecosystem-level logo mark is registered in the registry yet** — that's an open identity\n item. Until one exists, ecosystem-level materials use the wordmark in copper, optionally with\n Mukoko's Swarm mark (a Nyuchi asset, not in this package) when the subject is the product family. Don't invent an ecosystem mark ad hoc.\n\n### The brand constellation (verified, v4.1.0)\n\nEvery brand owns a mineral and a voice. Theme each brand's surfaces and copy in **its own** mineral\nand register — never default everything to tanzanite.\n\n| Brand | Role | Mineral | Meaning | Voice |\n| ------------ | --------------------------- | ---------- | ---------- | -------------------------------------- |\n| **bundu** | The ecosystem | copper | Wilderness | visionary, grounded, inclusive |\n| **nyuchi** | Infrastructure & enterprise | gold | Bee | technical, reliable, industrious |\n| **mukoko** | Africa's super app | tanzanite | Beehive | welcoming, structured, protective |\n| **shamwari** | Sovereign AI companion | sodalite | Friend | helpful, warm, intelligent |\n| nhimbe | Events & gatherings | malachite | Gathering | celebratory, communal, vibrant |\n| bushtrade | Marketplace | gold | Bush trade | practical, trustworthy, local |\n| lingo | Language learning | cobalt | Language | encouraging, cultural, playful |\n| campfire | Platform messaging anchor | malachite | Campfire | direct, warm, always present |\n| bytes | Short-form creator video | tanzanite | Bytes | energetic, creative, youthful |\n| novels | Publishing platform | malachite | Novels | literary, thoughtful, immersive |\n| places | Geographic knowledge graph | gold | Places | authoritative, helpful, discoverable |\n| transport | Public transit & booking | gold | Transport | efficient, reliable, practical |\n| planner | Productivity hub | cobalt | Planner | organised, clear, supportive |\n| wallet | Payments & tokens | gold | Wallet | trustworthy, precise, secure |\n| pulse | Feed & Mukoko Home | tanzanite | Pulse | personal, adaptive, ambient |\n| health | Wellness & telemedicine | malachite | Health | caring, accurate, private |\n| circles | Community messaging | terracotta | Circles | inclusive, moderated, community-driven |\n\nThe `bundu` row is the ecosystem layer as `/v1/brand` serves it today; the table mirrors the\nregistry, so it changes when mzizi-registry renames the entry.\n\nThe narrative spine: **nyuchi is the bee, mukoko is the hive, Mzizi is the root everything\ngrows from.** Use this when explaining the ecosystem — it is the one-sentence architecture.\n\n### Ubuntu as identity (live doctrine)\n\nMzizi carries the philosophy. _Ndiri nekuti tiri_ — I am because we are.\n\n**Five Pillars** (spheres of belonging): **family** (the household and kin, the primary web of\nbelonging) → **community** (neighbours, peers, chosen kin) → **society** (the broader human web —\nstrangers, institutions, the polity) → **environment** (the natural world that holds all\nrelationships; Ubuntu is ecological before it is social) → **spirituality** (the vertical\ndimension — meaning, purpose, ancestry, the sacred).\n\n**Five Principles** (operating rules): **survival** (collective endurance — no one survives alone) ·\n**solidarity** (firm, persevering commitment to the common good) · **compassion** (active care shown\nthrough helping, sharing, welcoming) · **respect** (unprejudiced consideration of another's\nprivileges, beliefs, norms — includes elder-respect) · **dignity** (inherent worth by virtue of\nexistence, irrespective of status or background).\n\nIn ecosystem materials, philosophy is **structural, not decorative**: name a pillar/principle only\nwhen the feature or message genuinely embodies it.\n\n### Rules for ecosystem-level work\n\n- **Wordmarks:** `Mzizi` is capitalised. Every other wordmark is lowercase: `nyuchi`, `mukoko`,\n `shamwari`, `bundu`, `nhimbe` and every sister brand.\n- **Copper accents** signal \"this is the ecosystem speaking\" (parent-level pages, governance,\n commons, manifesto-register material).\n- **Cross-brand layouts:** lead with the brand that owns the message; bring in the constellation\n table's minerals when showing the family together (each brand in its own mineral).\n- **Voice shifts with the speaker.** An ecosystem-level page is visionary/grounded/inclusive; the same content\n on mukoko.com becomes welcoming/structured/protective. Don't copy-paste tone across brands.\n- Cultural specificity is non-negotiable: Shona vocabulary and Zimbabwean grounding are structural.\n- All UI tokens (radius, type, spacing, surfaces, status colours) are under \"The design system\"\n below. Mukoko's logo and assets are Nyuchi's and are not in this package.\n- **Every brand owns a mineral, but the palette is not only minerals.** A cross-brand piece has 21\n families to draw on — seven minerals, seven Heritage tones, the Experimental Seven. Use the\n minerals to say _who is speaking_; use Heritage for atmosphere and surface, and the Experimental\n Seven for categorical sets that would otherwise stretch a mineral past its meaning.\n\n## The design system\n\n> **Doctrine v4.1.0. The canonical palette ships with this package.**\n> `palette.canonical.json` at the root of `@nyuchi/mzizi-skills` states all **21 colour\n> families** — seven African Minerals, seven Heritage tones, the Experimental Seven — and\n> every token file in this bundle is generated from it and gated against it. Read it, or the\n> stylesheets generated from it (`tokens-update.css` and `tokens/palette.css` here). Do not retype hex values out of this prose into a project.\n>\n> Canon upstream is `mzizi-dev/mzizi-registry → lib/tokens/palette.source.ts`, served publicly\n> at <https://api.mzizi.dev/v1/brand> and by the MCP as `mzizi_get_tokens`. There is no\n> database: the registry's files are the data layer.\n>\n> **Colour is derived and gated. Everything else on this page is hand-maintained** —\n> typography, spacing, radius, sizes, motion, shadow, z-index and the semantic set. The\n> radius scale and control sizes were checked against `mzizi-registry` `main` for 0.8.0;\n> confirm the rest with `mzizi_get_tokens` (`typography`, `spacing`, `radii`, `semantic`,\n> `componentSpecs`) if precision matters.\n\n### Rules that are non-negotiable\n\n- **Wordmarks** — `Mzizi` is capitalised. `mukoko`, `nyuchi`, `bundu`, `shamwari`, `nhimbe`, `bushtrade` are lowercase, always.\n- **The palette is 21 families: seven Minerals + seven Heritage + the Experimental Seven.** Any interface that uses only the minerals is using a third of the system. Do not describe it as \"the seven-colour palette\".\n- **Seven African Minerals** (geological, the core palette). The custom properties are `--color-<mineral>`, `--color-<mineral>-container`, `--color-<mineral>-on-container` — those are the names the design system defines. (`--container-<mineral>` / `--on-container-<mineral>` appeared in earlier revisions of this skill and are defined nowhere upstream; `tokens-update.css` now ships them as aliases so existing work keeps rendering, but do not reach for them in new code.)\n - `cobalt` — primary blue, links, CTAs · digital future, trust\n - `tanzanite` — **mukoko's brand mineral**, social features · premium, connection\n - `malachite` — success states, positive actions · growth\n - `gold` — achievements, rewards, highlights · honey, warmth\n - `terracotta` — community features · earth, grounding\n - `sodalite` — **AI / Shamwari surfaces, deep-reasoning states** · intelligence, depth _(added v4.1.0)_\n - `copper` — **Bundu ecosystem identity, the commons** · connection, stewardship _(added v4.1.0)_\n- **Seven Heritage tones** (atmospheric anchors — mini-app surfaces, backgrounds, moods; no family or role): `indigo` (dusk, the dyer's craft) · `savanna` (sun-dried grass, the dry season) · `baobab` (the tree of life, bark, shelter) · `sunset` (day's end, the gathering hour) · `river` (flow, the journey) · `hematite` (the neutral anchor, the substrate) · `kalahari` (the light anchor, openness). Reach for them via `--color-<name>` (aliasing `--heritage-<name>`); values are in `palette.canonical.json`. _(Two earlier framings are withdrawn: the ten-colour one from before the palette settled, and the two-anchor one that survived into v0.5.1 of this bundle. **There are seven.** A skill that named two produced two-tone output in projects that had never seen each other.)_\n- **The Experimental Seven** — a computed heptagon (hues offset 17°, prime saturations, foregrounds solved to P7), each tone carrying a `heptagonIndex` 0–6 that fixes its place on the wheel: `ember` · `acacia` · `fern` · `lagoon` · `storm` · `dusk` · `protea`. Four tiers each: `--exp-<name>`, `--exp-<name>-container`, `--exp-<name>-on`, `--exp-<name>-ui`. Use them for generated/wheel-derived surfaces and categorical sets where the minerals would have to be stretched past their meanings.\n- **Each brand owns a mineral** (the ecosystem map, verified v4.1.0 and served at `/v1/brand` as `.ecosystem`): bundu→**copper**, nyuchi→**gold**, mukoko→**tanzanite**, shamwari→**sodalite**, nhimbe/campfire/novels/health→malachite, lingo/planner→cobalt, bushtrade/places/transport/wallet→gold, bytes/pulse→tanzanite, circles→terracotta. Theme a surface in its brand's mineral — never default everything to tanzanite.\n- **Buttons are ALWAYS pill** (`rounded-full` → `--radius-full`, 9999px). Inputs match buttons visually.\n- **Nothing tappable goes below 48px.** Buttons are `h-14` (56px) default and large, `h-12` (48px) small; icon buttons `size-14` / `size-12`. Inputs are 56px default, 48px small. That is what `button.tsx`, the Roots `button.rs` and the language's `primitives/button.mz` all ship, and what `/v1/brand` `.componentSpecs` serves (`minTouchTarget: 48`). (0.7.0 of this skill said controls were dense, 32/36/40px, with 56px only for hero CTAs. The shipped primitives do not do that, so that rule described a system that does not exist.) Badges are 20px tall and are not touch targets; a small element that becomes one gets `min-h-[48px]`.\n- **Radius scale** (px), every step from a 7px unit: `sm` 7 · `md` 12 · `lg` 14 (**DEFAULT** cards/panels) · `xl` 17 · `2xl` 17 · `full` 9999 (pills). That is `mzizi-registry/app/globals.css`, the Rust `mzizi-tokens` `Radius`, and `/v1/brand` `.radii` (\"ecosystem numbers: 7, 12, 14, 17\"). Checkboxes use `sm`, inputs and small cards `md`, cards and panels `lg`, modals, sheets, dialogs and tabs `xl`. Reference via `--radius-<name>`. (Until 0.8.0 this skill also listed `none` 0, `xs` 4 and `2xl` 24; none of those is defined upstream.)\n- **H1–H3 are Noto Serif. H4+ and body are Noto Sans. Code is JetBrains Mono.**\n- **Rings over shadows.** Cards get `ring-1` of `--border`, not a drop shadow.\n- **No gradients, no glassmorphism** except sticky chrome (`backdrop-blur`).\n- **Emoji only as mini-app identifiers**, never as chrome or body decoration.\n- **Status has its own names: `--status-success` / `--status-warning` / `--status-error` / `--status-info` / `--status-neutral`.** Use those names and do not hardcode a hex. Upstream they alias `--success` / `--warning` / `--destructive` / `--info` / `--neutral`, so there is one decision per status — which means the resolved values are mineral-derived in the shipped theme (success reads as malachite, info as cobalt), not the standalone #22C55E/#F59E0B/#EF4444/#3B82F6 set an earlier revision of this skill listed. Those hexes are the per-component `var(--token, #hex)` fallback for an app that has not defined the properties; they are not the theme. Severity scale (`--severity-low/moderate/high/severe/extreme/cold`) and connection (`--connection-online/syncing/cached/offline`) follow the same logic. Verification tiers are the only place minerals carry status meaning (community→malachite, otp→cobalt, government→tanzanite, licensed→gold).\n- **Surfaces (April 2026 AAA refresh):** light mode — `--background` warm paper, `--card` pure white, `--muted` cream, `--overlay` white. Dark mode — `--background` warm stone L10%, `--card` L6% (darker than bg), `--muted` L2% (deepest), `--overlay` L14% (lighter than bg, scrim creates pop).\n- **Icons:** in the React build, Lucide through `@/lib/icons`, never `lucide-react` directly; in Mzizi Roots, an icon is an `Element` slot the host renders. For an HTML prototype, the Lucide CDN build is fine.\n- **APCA 3.0 AAA contrast** on both primary and secondary text on every surface.\n\n### When creating visual artifacts\n\nCreate static HTML files for the user to view. `@import` `tokens-update.css` at the top of every HTML file (or inline it). For slide decks use a mineral accent strip and serif display type.\n\n### When working on production code\n\nUse the registry's components, never hand-rolled copies. **Rust first:** Mzizi Roots is the lead build (`cargo add mzizi-roots`; see the `mzizi-roots` skill). The React build installs by absolute URL, for existing React apps:\n\n```bash\nnpx shadcn@latest add https://api.mzizi.dev/v1/ui/<name>\n```\n\nTake CSS variables from `tokens-update.css` or `tokens/palette.css` in this skill — it is generated from `palette.canonical.json` and carries all 21 families. The upstream equivalent is `mzizi-registry/app/globals.css`, which is generated from the same canon. There is no database to refresh from.\n\n## What belongs in the ecosystem\n\n### Ubuntu alignment\n\nThe ecosystem is built on Ubuntu — \"I am because we are.\" When you architect a new app:\n\n- Prefer open source over proprietary\n- Build for community, not extraction\n- Respect data sovereignty — African data stays on African infrastructure where possible\n- Accessibility is not an afterthought; it is the starting point\n- Every feature should contribute to collective wellbeing, not just individual efficiency\n\nThese aren't marketing claims, they're architectural constraints. If you find yourself\ndesigning something that only benefits a small subset of users at others' expense, question\nwhether it belongs in the ecosystem.\n\n### What is and isn't an ecosystem app\n\nThe ecosystem has two layers. **Mzizi** owns and operates the framework, the language, the\nregistry, the design system, the docs and the API, and sits under every app. **Nyuchi Africa**\noperates the Mzizi console and everything revenue-generating. Inside Nyuchi Africa\nsit three pillars and the platform:\n\n- **mukoko** — the consumer pillar: the super-app (social, commerce, payments, identity)\n- **nyuchi** — the commercial/infrastructure pillar and the platform (web services, developer\n tools, storage)\n- **shamwari ai** — the intelligence pillar, sovereign by nature (on-device inference,\n pod-resident context, no third-party AI vendor)\n- **nhimbe** — cooperative economic features\n\nMzizi is not an app and not a layer in a stack; it is the open, non-revenue foundation the\necosystem builds on and the doctrine that governs it. mukoko and shamwari ai are pillars inside Nyuchi Africa, not separate companies.\n\nNot ecosystem apps:\n\n- Any app that extracts value without giving back\n- Any app that requires surveillance for its business model\n- Any app that centralises what should be federated\n\nIf the app you're building doesn't fit, that's fine — consume the Mzizi design system, but\ndon't brand it as ecosystem.\n\n## If invoked with no other guidance\n\nAsk what the work is: (a) an interface or prototype (which surface: mobile, desktop, print,\nslide; which brand voice: Mzizi, nyuchi, mukoko, shamwari, nhimbe), (b) parent-brand material\n(ecosystem page, manifesto, deck), (c) a cross-brand piece, or (d) a voice or mineral decision\nfor one brand. Then apply the constellation table, the voice rules and the design system, and\noutput HTML artifacts or production code as the need requires.\n\n_Mzizi — the root everything grows from._",
28
+ "category": "design",
29
+ "description": "The Mzizi design system and brand: the canonical 21-family palette (seven African Minerals + seven Heritage tones + the Experimental Seven) with generated stylesheets (tokens/palette.css, tokens/minerals.css, tokens-update.css), the radius scale, Noto Sans/Serif + JetBrains Mono type, pill buttons and the 48px touch floor, status and surface tokens, the Dashboard and Discover Standards' brand overlays (colour and mark only), the verified brand constellation (Mzizi's own colour is hematite, a Heritage tone), the ecosystem identity (copper), the five Ubuntu pillars + five principles, what belongs in the ecosystem, and the wordmarks. Replaces nyuchi-design and half of ecosystem-app-setup, and carries the tokens that were in mukoko-design.",
30
+ "body_mdx": "# Mzizi design — the system and the ecosystem\n\nThis skill replaces three 0.7.0 skills: `nyuchi-design` (UI and tokens, now \"The design\nsystem\" below), the Ubuntu and ownership half of `ecosystem-app-setup` (now \"What belongs in\nthe ecosystem\"), and the tokens of `mukoko-design`, which moved here. The mukoko brand assets\n(the Swarm mark, logos, icons) are no longer in this package; they move to a Nyuchi-owned\nbundle.\n\n## Files this skill ships\n\nAll generated from `palette.canonical.json` at the package root by\n`scripts/generate-tokens.mjs`, and gated against it. Never hand-edit them; never retype a hex.\n\n| File | Contents |\n| ---------------------- | ---------------------------------------------------------------------------------------------------- |\n| `tokens/palette.css` | all 21 families, light + dark, as CSS custom properties |\n| `tokens/palette.json` | all 21 families, machine-readable |\n| `tokens/minerals.css` | the seven minerals only |\n| `tokens/minerals.json` | the seven minerals only, machine-readable |\n| `tokens-update.css` | all 21 families (light, dark, `.dark`, `prefers-color-scheme`) plus the radius scale, as one drop-in |\n\n## The ecosystem\n\n**Mzizi** is the parent identity at the ecosystem level — the Mzizi programming language, its\ntoolchain, the design language, the component registry and the open doctrine every brand builds\non. Everything revenue-generating (the\nMzizi console, fundi, paid plans, billing) is Nyuchi's. Three pillars: **mukoko** (consumer super app), **nyuchi** (infrastructure &\nenterprise), and the **sister brands** (specialist verticals) — one identity, one design system, one\nopen data commons.\n\nDoctrine **v4.1.0**. **The repo is the source of truth, and there is no database.** Colour\ncomes from `palette.canonical.json` at the root of this package — 21 families, vendored from\n`mzizi-dev/mzizi-registry → lib/tokens/palette.source.ts` and gated against it. The ecosystem\nconstellation and the Ubuntu pillars and principles come from `mzizi-registry →\ncontent/doctrine/`, served at <https://api.mzizi.dev/v1/brand> (`.ecosystem`, `.philosophy`) and\nby the MCP as `mzizi_get_tokens` (`family: \"ecosystem\"`) and `mzizi_get_doctrine`. Two notes:\nprinciples were renamed in v4.x, so use the set below and not older Shona-named lists; and the\nregistry still names its ecosystem-layer entry `bundu` (URL **bundu.org**). That is registry\ndata, and it changes in mzizi-registry, not here.\n\nThe **five** pillars and **five** principles below are a count of Ubuntu doctrine, not of the\npalette, and they are correct — `/v1/brand` `.philosophy.ubuntuPillars` serves exactly these five.\nDo not \"fix\" them to seven to match the colour families.\n\n### The ecosystem-level identity\n\n- **Mineral:** `copper` — `#BF5A36` light / `#FF8A65` dark (`--color-copper`) · connection, foundation, stewardship\n- **Registry entry:** `bundu` in `/v1/brand` `.ecosystem` (meaning _wilderness_, Shona) · **URL:** <https://bundu.org>\n- **Voice:** visionary, grounded, inclusive\n- **Role:** the ecosystem — the layer that holds the whole, the commons, governance, philosophy\n- **Wordmark:** `Mzizi`, capitalised, Noto Serif 600. (The other wordmarks stay lowercase.)\n- **No standalone ecosystem-level logo mark is registered in the registry yet** — that's an open identity\n item. Until one exists, ecosystem-level materials use the wordmark in copper, optionally with\n Mukoko's Swarm mark (a Nyuchi asset, not in this package) when the subject is the product family. Don't invent an ecosystem mark ad hoc.\n- **Copper is the ecosystem layer, not Mzizi's own surfaces.** mzizi.dev, docs.mzizi.dev, the\n registry and the language are the `mzizi` row of the constellation below: theme them in\n **hematite** (owner decision, 2026-09-30). Keep copper for material where the ecosystem as a\n whole speaks: governance, the commons, the manifesto register.\n\n### The brand constellation (verified, v4.1.0)\n\nEvery brand owns a mineral and a voice. Theme each brand's surfaces and copy in **its own** mineral\nand register — never default everything to tanzanite.\n\n| Brand | Role | Mineral | Meaning | Voice |\n| ------------ | ------------------------------------------- | ---------- | ---------- | -------------------------------------- |\n| **bundu** | The ecosystem | copper | Wilderness | visionary, grounded, inclusive |\n| **nyuchi** | Infrastructure & enterprise | gold | Bee | technical, reliable, industrious |\n| **mukoko** | Africa's super app | tanzanite | Beehive | welcoming, structured, protective |\n| **shamwari** | Sovereign AI companion | sodalite | Friend | helpful, warm, intelligent |\n| **Mzizi** | The root: language, registry, design system | hematite ¹ | Root | precise, honest, grounded |\n| events | Events & gatherings (Mukoko Events) | malachite | Gathering | celebratory, communal, vibrant |\n| bushtrade | Marketplace | gold | Bush trade | practical, trustworthy, local |\n| lingo | Language learning | cobalt | Language | encouraging, cultural, playful |\n| campfire | Platform messaging anchor | malachite | Campfire | direct, warm, always present |\n| bytes | Short-form creator video | tanzanite | Bytes | energetic, creative, youthful |\n| novels | Publishing platform | malachite | Novels | literary, thoughtful, immersive |\n| places | Geographic knowledge graph | gold | Places | authoritative, helpful, discoverable |\n| transport | Public transit & booking | gold | Transport | efficient, reliable, practical |\n| planner | Productivity hub | cobalt | Planner | organised, clear, supportive |\n| wallet | Payments & tokens | gold | Wallet | trustworthy, precise, secure |\n| pulse | Feed & Mukoko Home | tanzanite | Pulse | personal, adaptive, ambient |\n| health | Wellness & telemedicine | malachite | Health | caring, accurate, private |\n| circles | Community messaging | terracotta | Circles | inclusive, moderated, community-driven |\n| kweli | Trust and verification | malachite | Truth | honest, rigorous, reassuring |\n| learning | Education (Nyuchi Learning) | gold | Learning | patient, practical, encouraging |\n| news | News and journalism | cobalt | News | clear, credible, balanced |\n| weather | Weather and alerts | cobalt | Weather | calm, precise, timely |\n\nThe `bundu` row is the ecosystem layer as `/v1/brand` serves it today; the table mirrors the\nregistry, so it changes when mzizi-registry renames the entry. The last four rows joined canon\non 2026-10-04 (owner decisions, mzizi-registry #409): kweli is malachite (it used to borrow\nMukoko's tanzanite), news and weather are cobalt, and **learning is gold** because every Nyuchi\nbrand is gold. The education industry category follows it: education is gold, not cobalt.\n\nThe `events` row is **Mukoko Events**, at `events.mukoko.com` (owner decision,\n2026-10-04). The _nhimbe_ brand is retired. `nhimbe` survives only as a deprecated alias of\n`events`: canon still resolves it, and `brand-nhimbe.css` re-exports `brand-events.css`, so\nnothing that names it breaks. New work names `events` and writes \"Mukoko Events\" in copy. The\nmineral is unchanged: malachite.\n\n¹ **Mzizi's brand colour is hematite, and hematite is a Heritage tone, not one of the seven\nminerals** (owner decision, 2026-09-30). `/v1/brand` serves it in `.ecosystem` as\n`{\"name\": \"mzizi\", \"meaning\": \"Root\", \"language\": \"Swahili\", \"mineral\": \"hematite\"}` and lists\nhematite under `.heritage`, not `.minerals`: the field is called `mineral`, but it names a\npalette family, usually a mineral. Mzizi owns and operates the language, the registry, the design\nsystem, the docs and the API; `/v1/brand` gives its role as \"Language, registry and design\nsystem\", and its voice as \"Precise, honest, grounded\". A Mzizi surface takes its primary from\nhematite, as the shipped stylesheets do: `@bundu/ui`'s `brand-mzizi.css` (which mzizi.dev uses)\nsets `--primary` to `var(--color-hematite)`, and the registry's `mzizi-tokens-globals.css`\ndefaults `--primary` to `var(--heritage-hematite-aa)`. Not gold (nyuchi's), not tanzanite (mukoko's), not copper (the\necosystem layer's). Take the values from `palette.canonical.json` (`hematite`, Heritage).\n\nThe narrative spine: **nyuchi is the bee, mukoko is the hive, Mzizi is the root everything\ngrows from.** Use this when explaining the ecosystem — it is the one-sentence architecture.\n\n### Ubuntu as identity (live doctrine)\n\nMzizi carries the philosophy. _Ndiri nekuti tiri_ — I am because we are.\n\n**Five Pillars** (spheres of belonging): **family** (the household and kin, the primary web of\nbelonging) → **community** (neighbours, peers, chosen kin) → **society** (the broader human web —\nstrangers, institutions, the polity) → **environment** (the natural world that holds all\nrelationships; Ubuntu is ecological before it is social) → **spirituality** (the vertical\ndimension — meaning, purpose, ancestry, the sacred).\n\n**Five Principles** (operating rules): **survival** (collective endurance — no one survives alone) ·\n**solidarity** (firm, persevering commitment to the common good) · **compassion** (active care shown\nthrough helping, sharing, welcoming) · **respect** (unprejudiced consideration of another's\nprivileges, beliefs, norms — includes elder-respect) · **dignity** (inherent worth by virtue of\nexistence, irrespective of status or background).\n\nIn ecosystem materials, philosophy is **structural, not decorative**: name a pillar/principle only\nwhen the feature or message genuinely embodies it.\n\n### Rules for ecosystem-level work\n\n- **Wordmarks:** `Mzizi` is capitalised. Every other wordmark is lowercase: `nyuchi`, `mukoko`,\n `shamwari`, `bundu` and every sister brand.\n- **Copper accents** signal \"this is the ecosystem speaking\" (parent-level pages, governance,\n commons, manifesto-register material).\n- **Cross-brand layouts:** lead with the brand that owns the message; bring in the constellation\n table's minerals when showing the family together (each brand in its own mineral).\n- **Voice shifts with the speaker.** An ecosystem-level page is visionary/grounded/inclusive; the same content\n on mukoko.com becomes welcoming/structured/protective. Don't copy-paste tone across brands.\n- Cultural specificity is non-negotiable: Shona vocabulary and Zimbabwean grounding are structural.\n- All UI tokens (radius, type, spacing, surfaces, status colours) are under \"The design system\"\n below. Mukoko's logo and assets are Nyuchi's and are not in this package.\n- **Every brand owns a mineral, but the palette is not only minerals.** A cross-brand piece has 21\n families to draw on — seven minerals, seven Heritage tones, the Experimental Seven. Use the\n minerals to say _who is speaking_ (and hematite when Mzizi is); use Heritage for atmosphere and surface, and the Experimental\n Seven for categorical sets that would otherwise stretch a mineral past its meaning.\n\n## The design system\n\n> **Doctrine v4.1.0. The canonical palette ships with this package.**\n> `palette.canonical.json` at the root of `@nyuchi/mzizi-skills` states all **21 colour\n> families** — seven African Minerals, seven Heritage tones, the Experimental Seven — and\n> every token file in this bundle is generated from it and gated against it. Read it, or the\n> stylesheets generated from it (`tokens-update.css` and `tokens/palette.css` here). Do not retype hex values out of this prose into a project.\n>\n> Canon upstream is `mzizi-dev/mzizi-registry → lib/tokens/palette.source.ts`, served publicly\n> at <https://api.mzizi.dev/v1/brand> and by the MCP as `mzizi_get_tokens`. There is no\n> database: the registry's files are the data layer.\n>\n> **Colour is derived and gated. Everything else on this page is hand-maintained** —\n> typography, spacing, radius, sizes, motion, shadow, z-index and the semantic set. The\n> radius scale and control sizes were checked against `mzizi-registry` `main` for 0.8.0;\n> confirm the rest with `mzizi_get_tokens` (`typography`, `spacing`, `radii`, `semantic`,\n> `componentSpecs`) if precision matters.\n\n**The published design system.** The brand book, the tokens as data, the marks and static\npreviews of the components (Badge, Button, Card, Checkbox, Input, StatusBadge, Switch, Tabs and\nmore) are published as the Design System artifact at\n<https://claude.ai/artifact/G8CCtAbZ8w717uQ3R5itCc>. Its source of truth is the `design-system/`\nfolder in `mzizi-dev/mzizi-registry` (arriving with mzizi-registry#418), which the artifact is\nbuilt from file for file. To change the design system, change that folder, never the artifact\npage: the artifact is republished from registry `main` after a merge that touches it.\n\n### Rules that are non-negotiable\n\n- **Wordmarks** — `Mzizi` is capitalised. `mukoko`, `nyuchi`, `bundu`, `shamwari`, `bushtrade` are lowercase, always. (Product names such as Mukoko Events are written as names, not wordmarks.)\n- **The palette is 21 families: seven Minerals + seven Heritage + the Experimental Seven.** Any interface that uses only the minerals is using a third of the system. Do not describe it as \"the seven-colour palette\".\n- **Seven African Minerals** (geological, the core palette). The custom properties are `--color-<mineral>`, `--color-<mineral>-container`, `--color-<mineral>-on-container` — those are the names the design system defines. (`--container-<mineral>` / `--on-container-<mineral>` appeared in earlier revisions of this skill and are defined nowhere upstream; `tokens-update.css` now ships them as aliases so existing work keeps rendering, but do not reach for them in new code.)\n - `cobalt` — primary blue, links, CTAs · digital future, trust\n - `tanzanite` — **mukoko's brand mineral**, social features · premium, connection\n - `malachite` — success states, positive actions · growth\n - `gold` — achievements, rewards, highlights · honey, warmth\n - `terracotta` — community features · earth, grounding\n - `sodalite` — **AI / Shamwari surfaces, deep-reasoning states** · intelligence, depth _(added v4.1.0)_\n - `copper` — **Bundu ecosystem identity, the commons** · connection, stewardship _(added v4.1.0)_\n- **Seven Heritage tones** (atmospheric anchors — mini-app surfaces, backgrounds, moods; no family or role, with one exception: `hematite` is Mzizi's brand colour): `indigo` (dusk, the dyer's craft) · `savanna` (sun-dried grass, the dry season) · `baobab` (the tree of life, bark, shelter) · `sunset` (day's end, the gathering hour) · `river` (flow, the journey) · `hematite` (the neutral anchor, the substrate) · `kalahari` (the light anchor, openness). Reach for them via `--color-<name>` (aliasing `--heritage-<name>`); values are in `palette.canonical.json`. _(Two earlier framings are withdrawn: the ten-colour one from before the palette settled, and the two-anchor one that survived into v0.5.1 of this bundle. **There are seven.** A skill that named two produced two-tone output in projects that had never seen each other.)_\n- **The Experimental Seven** — a computed heptagon (hues offset 17°, prime saturations, foregrounds solved to P7), each tone carrying a `heptagonIndex` 0–6 that fixes its place on the wheel: `ember` · `acacia` · `fern` · `lagoon` · `storm` · `dusk` · `protea`. Four tiers each: `--exp-<name>`, `--exp-<name>-container`, `--exp-<name>-on`, `--exp-<name>-ui`. Use them for generated/wheel-derived surfaces and categorical sets where the minerals would have to be stretched past their meanings.\n- **Each brand owns a mineral** (the ecosystem map, verified v4.1.0 and served at `/v1/brand` as `.ecosystem`): bundu→**copper**, nyuchi→**gold**, mukoko→**tanzanite**, shamwari→**sodalite**, Mzizi→**hematite** (a Heritage tone, not a mineral: the one brand whose colour comes from the Heritage set), events (Mukoko Events)/campfire/novels/health/kweli→malachite, lingo/planner/news/weather→cobalt, bushtrade/places/transport/wallet/learning→gold (learning, and with it the education category, is gold because every Nyuchi brand is), bytes/pulse→tanzanite, circles→terracotta. Theme a surface in its brand's colour — never default everything to tanzanite, and never give a Mzizi surface gold, tanzanite or copper as its primary.\n- **Buttons are ALWAYS pill** (`rounded-full` → `--radius-full`, 9999px). Inputs match buttons visually. The exception is the Mzizi Dashboard Standard (below): its app Button and controls use the small radius, as their contracts state.\n- **Nothing tappable goes below 48px.** Buttons are `h-14` (56px) default and large, `h-12` (48px) small; icon buttons `size-14` / `size-12`. Inputs are 56px default, 48px small. That is what `button.tsx`, the Roots `button.rs` and the language's `primitives/button.mz` all ship, and what `/v1/brand` `.componentSpecs` serves (`minTouchTarget: 48`). (0.7.0 of this skill said controls were dense, 32/36/40px, with 56px only for hero CTAs. The shipped primitives do not do that, so that rule described a system that does not exist.) Badges are 20px tall and are not touch targets; a small element that becomes one gets `min-h-[48px]`. Dashboards are the exception: the Dashboard Standard's app components are dense on a fine pointer (most controls 32–40px) and 44–48px on touch, each as its contract's `density` states. The Discover Standard's components are the same size on every pointer: 48px for the search field and button, \"See all\", \"Load more\" and \"Open in Mukoko\", 44px for category chips, navigation links and the home link.\n- **Radius scale** (px), every step from a 7px unit: `sm` 7 · `md` 12 · `lg` 14 (**DEFAULT** cards/panels) · `xl` 17 · `2xl` 17 · `full` 9999 (pills). That is `mzizi-registry/styles/globals.css`, the Rust `mzizi-tokens` `Radius`, and `/v1/brand` `.radii` (\"ecosystem numbers: 7, 12, 14, 17\"). Checkboxes use `sm`, inputs and small cards `md`, cards and panels `lg`, modals, sheets, dialogs and tabs `xl`. Reference via `--radius-<name>`. (Until 0.8.0 this skill also listed `none` 0, `xs` 4 and `2xl` 24; none of those is defined upstream.)\n- **H1–H3 are Noto Serif. H4+ and body are Noto Sans. Code is JetBrains Mono.**\n- **Rings over shadows.** Cards get `ring-1` of `--border`, not a drop shadow.\n- **No gradients, no glassmorphism** except sticky chrome (`backdrop-blur`) and the Discover Standard's hero wash (DiscoverHero's `gradient-showcase`, which mixes `--primary` into the background, as its contract declares).\n- **Emoji only as mini-app identifiers**, never as chrome or body decoration.\n- **Status has its own names: `--status-success` / `--status-warning` / `--status-error` / `--status-info` / `--status-neutral`.** Use those names and do not hardcode a hex. Upstream they alias `--success` / `--warning` / `--destructive` / `--info` / `--neutral`, so there is one decision per status — which means the resolved values are mineral-derived in the shipped theme (success reads as malachite, info as cobalt), not the standalone #22C55E/#F59E0B/#EF4444/#3B82F6 set an earlier revision of this skill listed. Those hexes are the per-component `var(--token, #hex)` fallback for an app that has not defined the properties; they are not the theme. Severity scale (`--severity-low/moderate/high/severe/extreme/cold`) and connection (`--connection-online/syncing/cached/offline`) follow the same logic. Verification tiers are the only place minerals carry status meaning (community→malachite, otp→cobalt, government→tanzanite, licensed→gold).\n- **Surfaces (April 2026 AAA refresh):** light mode — `--background` warm paper, `--card` pure white, `--muted` cream, `--overlay` white. Dark mode — `--background` warm stone L10%, `--card` L6% (darker than bg), `--muted` L2% (deepest), `--overlay` L14% (lighter than bg, scrim creates pop).\n- **Icons:** in the React build, Lucide through `@/lib/icons`, never `lucide-react` directly; in Mzizi Roots, an icon is an `Element` slot the host renders. For an HTML prototype, the Lucide CDN build is fine.\n- **APCA 3.0 AAA contrast** on both primary and secondary text on every surface.\n\n### When creating visual artifacts\n\nCreate static HTML files for the user to view. `@import` `tokens-update.css` at the top of every HTML file (or inline it). For slide decks use a mineral accent strip and serif display type.\n\n### When working on production code\n\nUse the registry's components, never hand-rolled copies. **Rust first:** Mzizi Roots is the lead build (`cargo add mzizi-roots`; see the `mzizi-roots` skill). The React build installs by absolute URL, for existing React apps:\n\n```bash\nnpx shadcn@latest add https://api.mzizi.dev/v1/ui/<name>\n```\n\nTake CSS variables from `tokens-update.css` or `tokens/palette.css` in this skill — it is generated from `palette.canonical.json` and carries all 21 families. The upstream equivalent is `mzizi-registry/styles/globals.css`, which is generated from the same canon. There is no database to refresh from.\n\n### Dashboards: brand overlays only\n\nAn admin or operator dashboard uses the **Mzizi Dashboard Standard**: the app components of\n`@bundu/ui` (`@bundu/ui/app/*`), one design for every product in the Bundu ecosystem, each\nwith a versioned contract in `mzizi-registry/contracts/app/` (see `mzizi-roots`). A brand\nchanges colour and mark, and nothing else:\n\n- **A brand overlay changes `--primary`, `--ring`, AppShell's `--app-accent` and the brand\n mark.** Layout, spacing, type and size never vary by brand. The overlay is one stylesheet\n imported after the tokens (`@bundu/ui/styles/brand-<brand>.css`). AppShell's `accent`\n takes one of the seven minerals and fills primary actions and the current nav indicator\n with its brand fill (`--color-<mineral>-brand`, text `--color-<mineral>-on-brand`); text\n links and the focus ring keep `--primary` and `--ring`.\n- **Status colours do not follow the brand.** Success is malachite, warning gold, error\n terracotta, info cobalt, in every brand. A component names a mineral only as one of its\n contract's `theming.statusColours`; any other mineral class, or a literal colour value,\n fails its contract test.\n- **Mzizi's hematite** is a Heritage tone, so AppShell's `accent` does not apply to it;\n `brand-mzizi.css` sets `--primary`.\n\n### Discover pages: the same overlay, touch-first\n\nA public discover or browse page in the Mukoko family (circles, news, events, weather, and the\nsuper-app on the web) uses the **Mzizi Discover Standard**: the Discover components of\n`@bundu/ui` (`@bundu/ui/discover/*`), each with a versioned contract in\n`mzizi-registry/contracts/discover/` (see `mzizi-roots`). The brand-overlay rule is the\nDashboard Standard's, with two differences the contracts state:\n\n- **No `--app-accent`.** A brand shows in DiscoverShell's `brand` slot (its mark) and in the\n primary action its `action` slot holds; the frame, header, navigation and footer are\n identical in every brand. `--primary` reaches only DiscoverHero's wash, DiscoverCard's\n monogram and `brand` badge tone, and OpenInApp's `primary` variant. Category chips, sections,\n search and \"Load more\" are neutral (search follows the brand only in its `--ring` focus\n ring). DiscoverCard is the one Discover component with status colours (`cobalt`,\n `malachite`, `gold`, `terracotta`), for its badge tones, and they do not follow the brand.\n- **The same size on every pointer.** Discover pages are not dense on a mouse: every density\n row in `contracts/discover/` gives the same height for a fine and a coarse pointer (48px or\n 44px, as above).\n\n## What belongs in the ecosystem\n\n### Ubuntu alignment\n\nThe ecosystem is built on Ubuntu — \"I am because we are.\" When you architect a new app:\n\n- Prefer open source over proprietary\n- Build for community, not extraction\n- Respect data sovereignty — African data stays on African infrastructure where possible\n- Accessibility is not an afterthought; it is the starting point\n- Every feature should contribute to collective wellbeing, not just individual efficiency\n\nThese aren't marketing claims, they're architectural constraints. If you find yourself\ndesigning something that only benefits a small subset of users at others' expense, question\nwhether it belongs in the ecosystem.\n\n### What is and isn't an ecosystem app\n\nThe ecosystem has two layers. **Mzizi** owns and operates the language, its toolchain, the\nregistry, the design system, the docs and the API, and sits under every app. **Nyuchi Africa**\noperates the Mzizi console and everything revenue-generating. Inside Nyuchi Africa\nsit three pillars and the platform:\n\n- **mukoko** — the consumer pillar: the super-app (social, commerce, payments, identity)\n- **nyuchi** — the commercial/infrastructure pillar and the platform (web services, developer\n tools, storage)\n- **shamwari ai** — the intelligence pillar, sovereign by nature (on-device inference,\n pod-resident context, no third-party AI vendor)\n- **Mukoko Events** (formerly nhimbe) — community events and gatherings\n\nMzizi is not an app and not a layer in a stack; it is the open, non-revenue foundation the\necosystem builds on and the doctrine that governs it. mukoko and shamwari ai are pillars inside Nyuchi Africa, not separate companies.\n\nNot ecosystem apps:\n\n- Any app that extracts value without giving back\n- Any app that requires surveillance for its business model\n- Any app that centralises what should be federated\n\nIf the app you're building doesn't fit, that's fine — consume the Mzizi design system, but\ndon't brand it as ecosystem.\n\n## If invoked with no other guidance\n\nAsk what the work is: (a) an interface or prototype (which surface: mobile, desktop, print,\nslide; which brand voice: Mzizi, nyuchi, mukoko, shamwari, Mukoko Events), (b) parent-brand material\n(ecosystem page, manifesto, deck), (c) a cross-brand piece, or (d) a voice or mineral decision\nfor one brand. Then apply the constellation table, the voice rules and the design system, and\noutput HTML artifacts or production code as the need requires.\n\n_Mzizi — the root everything grows from._",
26
31
  "source": "mzizi-dev/agent-tools/mzizi-skills/skills/mzizi-design"
27
32
  },
28
33
  {
29
34
  "name": "mzizi-backend",
30
- "description": "A Mzizi backend service on Cloudflare Workers: where it stands today (every live Mzizi Worker is TypeScript; the language has no handlers yet), the live endpoint map (api.mzizi.dev, mcp.mzizi.dev, docs.mzizi.dev/mcp), a Rust Worker as a sans-IO core plus thin host (workers-rs, wrangler, cron, queues, bindings, no database client), an MCP server (Streamable HTTP, one factory with many entrypoints, a code-defined catalogue over data bundled at a pinned commit, federation, naming and annotations, npm + MCP Registry publish), the free-except-Fundi access rule, logging and deploy. Replaces cloudflare-worker-rust and mcp-server-cloudflare.",
31
- "body_mdx": "# Mzizi backend on Cloudflare Workers\n\nThis skill replaces two 0.7.0 skills: `cloudflare-worker-rust` (now \"A Rust Worker\") and\n`mcp-server-cloudflare` (now \"An MCP server\"). The rules they shared (access, logging, deploy)\nare stated once, at the end.\n\n## Where the backend stands today\n\n**Direction: Rust first, TypeScript second** (`mzizi-registry` →\n`content/doctrine/documentation-architecture-framework/`), and Mzizi's backend is meant, in\nthe end, to be built in the Mzizi language itself (CHARTER v0.3). Neither is the state today,\nand a skill that implied otherwise would mislead you:\n\n- **Every Worker Mzizi runs is TypeScript.** `api.mzizi.dev` is `mzizi-dev/mzizi-api-gateway`,\n a Hono Worker. `mzizi-dev/agent-tools` holds `mzizi-mcp` (mcp.mzizi.dev), `mzizi-fundi` and\n `bushtrade-mcp`, all TypeScript, because the MCP SDK, `hono` and `agents` are TypeScript.\n- **The Rust building blocks exist.** Mzizi Roots server components (the `mzizi-roots-server`\n crate: `mzizi-assurance`, `mzizi-fundi`, `mzizi-docs`, `mzizi-discovery`) are sans-IO cores\n meant to be mounted in a Worker. The `workers-rs` adapter crate (`mzizi-worker`) is planned\n and has not landed, so today the Worker writes its own few lines of adapter.\n- **The Mzizi language has no handlers yet.** RFC-0011 (handlers) is a draft; nothing lowers to\n Rust. See the `mzizi-language` skill.\n\nSo: write a new service's logic in Rust as a sans-IO core. Use TypeScript where the work leans\non the MCP SDK or `agents` (see \"An MCP server\" below). Port nothing that works\nwithout a reason; when a service is ported, its existing tests and parity script are the\ncontract the port must meet.\n\n## The live endpoint map\n\nRead this before inventing a hostname. Each MCP has its own host, and the old umbrella\n`mcp.nyuchi.dev/{service}` path scheme is **not** how these are deployed:\n\n| Endpoint | Worker / source | Serves |\n| -------------------------- | ----------------------------------------- | --------------------------------------------------------------------------------------------------- |\n| `api.mzizi.dev` | `mzizi-api-gateway` (Hono, TypeScript) | The registry API: the registry's files at a pinned commit; no origin, no database |\n| `mcp.mzizi.dev/mcp` | `mzizi-mcp` (`mzizi-dev/agent-tools`) | The Mzizi registry, tokens, architecture, doctrine and skills (`mzizi_*`), plus the docs (`docs_*`) |\n| `mcp.mzizi.dev/mcp/signin` | the same Worker | The same MCP, answering 401 until you sign in: the URL for the Fundi tools |\n| `docs.mzizi.dev/mcp` | Mintlify (`mzizi-dev/mzizi-docs`) | The Mzizi docs; `mzizi-mcp` federates these as `docs_*` |\n| (Worker, no public route) | `bushtrade-mcp` (`mzizi-dev/agent-tools`) | Agentic commerce: task-driven UCP/AP2 verbs (Nyuchi) |\n| `docs.nyuchi.com/mcp` | `nyuchi-docs-mcp` (`nyuchi/nyuchi-docs`) | Nyuchi's own docs corpus, not Mzizi's |\n\n`design.nyuchi.com` is a 308 permanent redirect to `mzizi.dev`. Never reference it in new code\nor docs. `mzizi.dev/mcp` 308-redirects to `mcp.mzizi.dev/mcp` (method and body preserved), so\npoint new clients at `mcp.mzizi.dev/mcp` directly.\n\n**One MCP per surface.** Two endpoints over one corpus is two tool catalogues to keep in step,\ntwo access stories to explain, and a silent choice for every client author. That is why the\ndocs are federated into `mzizi-mcp` rather than listed as a second Mzizi MCP.\n\n## A Rust Worker\n\n### The shape: a sans-IO core and a thin host\n\nThe core owns routing and response shaping. The host owns I/O, bindings and secrets. This is\nthe shape of `n10-documentation/mzizi-docs-api.rs` in the registry, and it is what lets the\nsame core run in a Worker, in a container behind axum, and in a native unit test.\n\n```\nservice-name/\n├── src/\n│ ├── core.rs # routes, types, a Store trait — no `worker` import\n│ └── lib.rs # the Worker: reads bindings, calls the core, writes the response\n├── wrangler.toml\n└── Cargo.toml\n```\n\nThis example compiles against `worker` 0.8.7, natively and for `wasm32-unknown-unknown`:\n\n```toml\n[lib]\ncrate-type = [\"cdylib\", \"rlib\"]\n\n[dependencies]\nworker = \"0.8\"\n```\n\n```rust\n// src/core.rs\n//! The sans-IO core: typed request in, typed response out. No `worker` import,\n//! so it builds and tests natively and for wasm32 alike.\n\n/// Every route this service answers. An unhandled one is a compile error in `respond`.\n#[derive(Debug, PartialEq, Eq)]\npub enum Route {\n Health,\n Item(String),\n NotFound,\n}\n\n/// Parse a method and path into a route.\npub fn route(method: &str, path: &str) -> Route {\n match (method, path.trim_end_matches('/')) {\n (\"GET\", \"/health\") => Route::Health,\n (\"GET\", p) if p.starts_with(\"/v1/items/\") => Route::Item(p[\"/v1/items/\".len()..].to_string()),\n _ => Route::NotFound,\n }\n}\n\n/// What the core needs from the host, behind a trait so a test can pass an in-memory one.\npub trait Store {\n fn item(&self, id: &str) -> Option<String>;\n}\n\n/// Shape the answer: a status and a body. The host owns I/O, bindings and secrets.\npub fn respond(route: &Route, store: &dyn Store) -> (u16, String) {\n match route {\n Route::Health => (200, r#\"{\"ok\":true}\"#.to_string()),\n Route::Item(id) => match store.item(id) {\n Some(body) => (200, body),\n None => (404, r#\"{\"error\":\"not found\"}\"#.to_string()),\n },\n Route::NotFound => (404, r#\"{\"error\":\"no such route\"}\"#.to_string()),\n }\n}\n```\n\n```rust\n// src/lib.rs\nuse worker::*;\n\nmod core;\n\n/// A `Store` over data the Worker has already read from its bindings (KV, R2, D1).\nstruct Loaded(Option<String>);\n\nimpl core::Store for Loaded {\n fn item(&self, _id: &str) -> Option<String> {\n self.0.clone()\n }\n}\n\n#[event(fetch)]\nasync fn fetch(req: Request, env: Env, _ctx: Context) -> Result<Response> {\n let route = core::route(req.method().as_ref(), &req.path());\n // The host does the I/O the core asked for, then hands the result over.\n let loaded = match &route {\n core::Route::Item(id) => env.kv(\"ITEMS\")?.get(id).text().await?,\n _ => None,\n };\n let (status, body) = core::respond(&route, &Loaded(loaded));\n let headers = Headers::new();\n headers.set(\"content-type\", \"application/json\")?;\n Ok(Response::ok(body)?.with_status(status).with_headers(headers))\n}\n\n#[event(scheduled)]\nasync fn scheduled(_event: ScheduledEvent, _env: Env, _ctx: ScheduleContext) {\n console_log!(\"[example] scheduled run\");\n}\n```\n\nTest the core natively with an in-memory `Store`, and assert status codes, headers and bodies\nper route there. That is the contract a server component carries until the language's handler\ncontracts (RFC-0010, RFC-0011) are implemented.\n\n### Project setup\n\n```bash\nnpm install -g wrangler\ncargo install worker-build\ncargo generate cloudflare/workers-rs\n```\n\n### wrangler.toml\n\n```toml\nname = \"mzizi-example-service\"\nmain = \"build/worker/shim.mjs\"\ncompatibility_date = \"2026-09-01\"\n\n[build]\ncommand = \"cargo install -q worker-build && worker-build --release\"\n\n[[kv_namespaces]]\nbinding = \"ITEMS\"\nid = \"<namespace id>\"\n\n[[triggers.crons]]\ncron = \"*/5 * * * *\"\n\n[[queues.consumers]]\nqueue = \"mzizi-example-signals\"\nmax_batch_size = 10\n```\n\nKeep `compatibility_date` current; a stale date silently pins you to old runtime behaviour.\nSecrets are set out of band with `wrangler secret put <NAME>` and never committed.\n\n### Storage\n\nA Mzizi service holds **no database client**. Its data is files bundled at build time (the way\n`api.mzizi.dev` and `mcp.mzizi.dev` serve the registry at a pinned commit) or Workers bindings:\nKV, R2, D1, Durable Objects. Put each behind a trait in the core so the core never imports\n`worker`. Only the Mzizi console (`app.mzizi.dev`, operated by Nyuchi) uses Supabase, and\nnothing else in the estate should talk to it.\n\n### Queue consumers\n\nA queue consumer needs the crate's `queue` feature:\n`worker = { version = \"0.8\", features = [\"queue\"] }`.\n\n```rust\n#[event(queue)]\npub async fn queue(batch: MessageBatch<String>, _env: Env, _ctx: Context) -> Result<()> {\n for message in batch.messages()? {\n // hand message.body() to the core, then acknowledge\n message.ack();\n }\n Ok(())\n}\n```\n\n## An MCP server\n\n### Transport\n\nMCP over HTTP is **Streamable HTTP**, JSON-RPC 2.0:\n\n- `POST /mcp` — JSON-RPC requests\n- `GET /mcp` — SSE stream\n- `DELETE /mcp` — session cleanup\n- `OPTIONS /mcp` — CORS preflight\n\nA server exposes `tools/list` and `tools/call`, and optionally `resources/list` and\n`prompts/list`.\n\nUse `@modelcontextprotocol/sdk`. TypeScript is the right choice for an MCP server today,\nbecause the SDK, `hono` and `agents` are TypeScript-first; put any heavy logic in a Rust core\n(\"A Rust Worker\" above). Pin the SDK version deliberately: `agents` hard-pins\n`@modelcontextprotocol/sdk`, and letting your own declaration drift produces two incompatible\n`McpServer` classes in the type graph and a private-field mismatch at typecheck.\n\n### One factory, many entrypoints\n\nKeep the server definition free of any runtime binding, then wrap it per surface. This is what\nlets the same catalogue ship as a Worker, a generic fetch handler and a local stdio bin\n(`mzizi-mcp/src/`):\n\n```\nsrc/\n├── server.ts # createXMcpServer(deps) → McpServer ← the only place tools are defined\n├── http.ts # createXHttpHandler() — any fetch runtime\n├── worker.ts # Cloudflare Worker entry, bound to env\n└── stdio.ts # bin for local clients / the MCP Registry\n```\n\n### The catalogue is code; the data is bundled\n\n`mzizi-mcp` defines its tools in code (`src/tools.ts`) and reads its data from files bundled at\nbuild time: `scripts/generate-registry.mjs` fetches the registry at the exact commit in\n`registry.pin.json`, and `scripts/generate-skills.mjs` inlines `mzizi-skills/`. There is **no\ndatabase client, dependency, env var or secret** behind a read tool. To move the MCP to newer\nregistry content, change the pinned commit in a pull request; the diff is the review.\n\nThe gates are tests, not a live drift check:\n\n1. **A tool-surface suite** connects a real MCP `Client` to the factory over an in-memory\n transport and asserts the contract: the tool names and prefix, annotations, input and output\n schemas, and which tools need sign-in (`test/tool-surface.test.ts`, `free-tier.test.ts`).\n2. **A bundle suite** calls every tool against the real bundled data with the network disabled,\n and asserts that what a tool serves is byte-identical to the file it came from\n (`registry-bundle.test.ts`, `rust-first.test.ts`, `skills-bundle.test.ts`, `no-database.test.ts`).\n\n**Federating another MCP.** `mzizi-mcp` lists the docs MCP's tools under a `docs_` prefix and\nforwards each call, with a cached tool list and a clear tool error when the upstream fails\n(`src/docs-federation.ts`). Prefer that to copying another server's content.\n\n### Tool naming and annotations\n\nTools are `snake_case`, prefixed with the server (`mzizi_`), and named for the action:\n`mzizi_search`, `mzizi_get_component`, `mzizi_list_components`, `mzizi_get_tokens`,\n`mzizi_get_architecture`, `mzizi_get_doctrine`, `mzizi_get_skills`,\n`mzizi_check_accessibility`, `mzizi_report_issue`, `mzizi_fundi`, `mzizi_mcp_describe`. Read the\nlive list with `tools/list` rather than trusting a copy, this one included. Inputs take plain\nnames (`{ \"name\": \"button\" }`); the old `p_`-prefixed database parameters are gone.\n\nEvery tool carries a `title`, an `outputSchema`, and honest behaviour hints:\n\n- `readOnlyHint` — true only if the tool cannot mutate anything. An action that runs an audit\n or files a report is **not** read-only.\n- `destructiveHint` / `idempotentHint` / `openWorldHint` — set them deliberately; clients use\n them to decide what to auto-approve. A tool that reads only bundled data is\n `openWorldHint: false`.\n\nReturn `structuredContent` matching the declared `outputSchema`.\n\n### Static assets\n\nIf the Worker also serves `llms.txt`, `robots.txt` or `/.well-known/*` cards, put them behind an\n`[assets]` block so they resolve ahead of the OAuth handler; everything else falls through to\nthe server.\n\n### Instrumentation\n\nIf you report tool errors to an observability sink, do it fire-and-forget via\n`ctx.waitUntil`, so reporting never blocks or fails the response.\n\n### Publishing to npm and the MCP Registry\n\n`server.json` next to `package.json` is the source of truth, and `package.json#mcpName`\n**must** equal `server.json#name` or the registry refuses the publish. Registry names are\n`io.github.mzizi-dev/<server>`: the namespace is the GitHub owner of the repository the publish\nruns from, because `mcp-publisher login github-oidc` only authorises `io.github.<owner>/*`. The\nold `io.github.nyuchi/mzizi-mcp` entry is stale and no longer updated.\n\nRelease checklist:\n\n1. Bump the version everywhere it is stated: the constant the server reports in `serverInfo`\n (`MZIZI_MCP_VERSION` in `src/server.ts`), `package.json#version`, `server.json#version`,\n and `server.json#packages[0].version`. A mismatch here is the most common failed publish.\n2. Merge to `main`. `.github/workflows/publish-mzizi-mcp.yml` publishes to npm (skipped if the\n version is already there) and then runs `mcp-publisher login github-oidc` and\n `mcp-publisher publish`, treating \"this version already exists\" as success. OIDC means no\n registry secret. A merge that does not change the version is a no-op.\n\n## Access: free, except what acts as a person (every service)\n\nOwner decision, 2026-09-29: **the MCP server and the CLI are free, with no gate.** An anonymous\nsession can `initialize`, `tools/list` and call every tool except the Fundi ones. Gating starts\nat **Fundi**: `mzizi_fundi` and `mzizi_report_issue` act as a Mzizi console user.\n\nHow `mzizi-mcp` does it (`src/worker.ts`, `src/access.ts`):\n\n- A tokenless request to `/mcp` is routed around the OAuth provider, rate-limited per IP, and\n served with no user.\n- Any request with an `Authorization` header, and `/mcp/signin`, goes through\n `@cloudflare/workers-oauth-provider`, which validates the token and puts the WorkOS identity\n on `ctx.props`. A bad or expired token still gets the 401 that makes a client refresh.\n- The gated tools stay **listed** to an anonymous caller and answer with a tool error that\n starts `Sign-in required:` and says how to sign in. `access.ts` is the one list.\n\nWhen a person signs in, it is WorkOS Connect OAuth with PKCE (a public client, no secret). A\nmachine calling an internal service uses WorkOS M2M; only the Worker holds that secret, and it\nmints the token and proxies the call so a CLI never holds it. Auth happens **inline in the\nclient**, never by punting the user to a browser tab mid-task.\n\nValidate an inbound token against the WorkOS JWKS before any privileged work, and cache the\nJWKS; refetching per request is the classic latency bug. Return `401` for a missing or invalid\ntoken and `403` for a valid token without the required scope. Never collapse the two, or\ncallers cannot tell \"sign in\" from \"you can't do this\". Cron, queue and health routes are not\nuser-triggered and bypass the gate by design.\n\n## Logging (every service)\n\nPrefix every line with the service in brackets, as `mzizi-fundi` does with `[fundi]`, so it is\ngreppable across services:\n\n```rust\nconsole_log!(\"[example] cycle complete: {} records processed\", count);\nconsole_error!(\"[example] upstream error: {:?}\", e);\n```\n\nNever log a token, a secret, or a full request body.\n\n## Deploy (every service)\n\n```bash\nwrangler dev # local\nwrangler deploy # manual only; normally CI deploys\n```\n\nWire deploys to CI rather than a laptop, so what is live is what is merged. `mzizi-mcp` is\ndeployed by Cloudflare Workers Builds on every push to `main`, including its bundled registry\npin and skills. Run the test suites before you merge, and a live smoke pass of every read tool\n(`mzizi-mcp/scripts/integration.mjs`) after.",
35
+ "category": "build",
36
+ "description": "A Mzizi backend service on Cloudflare Workers: where it stands today (every live Mzizi Worker is TypeScript; the language's backend service slice, RFC-0011, runs in process under mz contract and lowers with mz build to a local Rust + axum package, with no Workers target and nothing deployed), the live endpoint map (api.mzizi.dev, mcp.mzizi.dev, docs.mzizi.dev/mcp), a Rust Worker as a sans-IO core plus thin host (workers-rs, wrangler, cron, queues, bindings, no database client), an MCP server (Streamable HTTP, one factory with many entrypoints, a code-defined catalogue over data bundled at a pinned commit, federation, naming and annotations, npm + MCP Registry publish), the free-except-Fundi access rule, logging and deploy. Replaces cloudflare-worker-rust and mcp-server-cloudflare.",
37
+ "body_mdx": "# Mzizi backend on Cloudflare Workers\n\nThis skill replaces two 0.7.0 skills: `cloudflare-worker-rust` (now \"A Rust Worker\") and\n`mcp-server-cloudflare` (now \"An MCP server\"). The rules they shared (access, logging, deploy)\nare stated once, at the end.\n\n## Where the backend stands today\n\n**Direction: Rust first, TypeScript second** (`mzizi-registry` →\n`content/doctrine/documentation-architecture-framework/`), and Mzizi's backend is meant, in\nthe end, to be built in the Mzizi language itself (`CHARTER.md` v0.4, §1). Neither is the\nstate today, and a skill that implied otherwise would mislead you:\n\n- **Every Worker Mzizi runs is TypeScript.** `api.mzizi.dev` is `mzizi-dev/mzizi-api-gateway`,\n a Hono Worker. `mzizi-dev/agent-tools` holds `mzizi-mcp` (mcp.mzizi.dev), `mzizi-fundi` and\n `bushtrade-mcp`, all TypeScript, because the MCP SDK, `hono` and `agents` are TypeScript.\n- **The Rust building blocks exist.** Mzizi Roots server components (the `mzizi-roots-server`\n crate: `mzizi-assurance`, `mzizi-fundi`, `mzizi-docs`, `mzizi-discovery`) are sans-IO cores\n meant to be mounted in a Worker. The `workers-rs` adapter crate (`mzizi-worker`) is planned\n and has not landed, so today the Worker writes its own few lines of adapter.\n- **The Mzizi language has one backend slice, and it serves nothing live.** A `service` with\n HTTP routes and handlers (`when`, `header`, `respond`) exists on the language's `main`\n (RFC-0011; `mzizi-dev/mzizi` at `62a0f32`). `mz check` checks it, `mz contract` runs it in\n process against its `example` and `ensure` clauses, and `mz build` lowers it to a local\n Rust + axum Cargo package that serves `127.0.0.1:$PORT`. That is all: no Workers,\n WebAssembly or Containers target, no deployment, no Workers bindings or storage, no request bodies,\n no state, and no expressions, functions, loops or modules in a handler. No component lowers.\n Its first job is the Phase 0 backend measurement (task B1, the `mzizi-be` arm), and no\n backend episode has run. Check `LANGUAGE-TRACKER.md` in the language repo before claiming\n any capability. \"A Mzizi service\" below shows the slice.\n\nSo: write a new production service's logic in Rust as a sans-IO core. Use TypeScript where\nthe work leans on the MCP SDK or `agents` (see \"An MCP server\" below). Port nothing that\nworks without a reason; when a service is ported, its existing tests and parity script are the\ncontract the port must meet.\n\n## The live endpoint map\n\nRead this before inventing a hostname. Each MCP has its own host, and the old umbrella\n`mcp.nyuchi.dev/{service}` path scheme is **not** how these are deployed:\n\n| Endpoint | Worker / source | Serves |\n| -------------------------- | ----------------------------------------- | --------------------------------------------------------------------------------------------------- |\n| `api.mzizi.dev` | `mzizi-api-gateway` (Hono, TypeScript) | The registry API: the registry's files at a pinned commit; no origin, no database |\n| `mcp.mzizi.dev/mcp` | `mzizi-mcp` (`mzizi-dev/agent-tools`) | The Mzizi registry, tokens, architecture, doctrine and skills (`mzizi_*`), plus the docs (`docs_*`) |\n| `mcp.mzizi.dev/mcp/signin` | the same Worker | The same MCP, answering 401 until you sign in: the URL for the Fundi tools |\n| `docs.mzizi.dev/mcp` | Mintlify (`mzizi-dev/mzizi-docs`) | The Mzizi docs; `mzizi-mcp` federates these as `docs_*` |\n| (Worker, no public route) | `bushtrade-mcp` (`mzizi-dev/agent-tools`) | Agentic commerce: task-driven UCP/AP2 verbs (Nyuchi) |\n| `docs.nyuchi.com/mcp` | `nyuchi-docs-mcp` (`nyuchi/nyuchi-docs`) | Nyuchi's own docs corpus, not Mzizi's |\n\n`design.nyuchi.com` is a 308 permanent redirect to `mzizi.dev`. Never reference it in new code\nor docs. `mzizi.dev/mcp` 308-redirects to `mcp.mzizi.dev/mcp` (method and body preserved), so\npoint new clients at `mcp.mzizi.dev/mcp` directly.\n\n**One MCP per surface.** Two endpoints over one corpus is two tool catalogues to keep in step,\ntwo access stories to explain, and a silent choice for every client author. That is why the\ndocs are federated into `mzizi-mcp` rather than listed as a second Mzizi MCP.\n\n## A Rust Worker\n\n### The shape: a sans-IO core and a thin host\n\nThe core owns routing and response shaping. The host owns I/O, bindings and secrets. This is\nthe shape of `n10-documentation/mzizi-docs-api.rs` in the registry, and it is what lets the\nsame core run in a Worker, in a container behind axum, and in a native unit test.\n\n```\nservice-name/\n├── src/\n│ ├── core.rs # routes, types, a Store trait — no `worker` import\n│ └── lib.rs # the Worker: reads bindings, calls the core, writes the response\n├── wrangler.toml\n└── Cargo.toml\n```\n\nThis example compiles against `worker` 0.8.7, natively and for `wasm32-unknown-unknown`:\n\n```toml\n[lib]\ncrate-type = [\"cdylib\", \"rlib\"]\n\n[dependencies]\nworker = \"0.8\"\n```\n\n```rust\n// src/core.rs\n//! The sans-IO core: typed request in, typed response out. No `worker` import,\n//! so it builds and tests natively and for wasm32 alike.\n\n/// Every route this service answers. An unhandled one is a compile error in `respond`.\n#[derive(Debug, PartialEq, Eq)]\npub enum Route {\n Health,\n Item(String),\n NotFound,\n}\n\n/// Parse a method and path into a route.\npub fn route(method: &str, path: &str) -> Route {\n match (method, path.trim_end_matches('/')) {\n (\"GET\", \"/health\") => Route::Health,\n (\"GET\", p) if p.starts_with(\"/v1/items/\") => Route::Item(p[\"/v1/items/\".len()..].to_string()),\n _ => Route::NotFound,\n }\n}\n\n/// What the core needs from the host, behind a trait so a test can pass an in-memory one.\npub trait Store {\n fn item(&self, id: &str) -> Option<String>;\n}\n\n/// Shape the answer: a status and a body. The host owns I/O, bindings and secrets.\npub fn respond(route: &Route, store: &dyn Store) -> (u16, String) {\n match route {\n Route::Health => (200, r#\"{\"ok\":true}\"#.to_string()),\n Route::Item(id) => match store.item(id) {\n Some(body) => (200, body),\n None => (404, r#\"{\"error\":\"not found\"}\"#.to_string()),\n },\n Route::NotFound => (404, r#\"{\"error\":\"no such route\"}\"#.to_string()),\n }\n}\n```\n\n```rust\n// src/lib.rs\nuse worker::*;\n\nmod core;\n\n/// A `Store` over data the Worker has already read from its bindings (KV, R2, D1).\nstruct Loaded(Option<String>);\n\nimpl core::Store for Loaded {\n fn item(&self, _id: &str) -> Option<String> {\n self.0.clone()\n }\n}\n\n#[event(fetch)]\nasync fn fetch(req: Request, env: Env, _ctx: Context) -> Result<Response> {\n let route = core::route(req.method().as_ref(), &req.path());\n // The host does the I/O the core asked for, then hands the result over.\n let loaded = match &route {\n core::Route::Item(id) => env.kv(\"ITEMS\")?.get(id).text().await?,\n _ => None,\n };\n let (status, body) = core::respond(&route, &Loaded(loaded));\n let headers = Headers::new();\n headers.set(\"content-type\", \"application/json\")?;\n Ok(Response::ok(body)?.with_status(status).with_headers(headers))\n}\n\n#[event(scheduled)]\nasync fn scheduled(_event: ScheduledEvent, _env: Env, _ctx: ScheduleContext) {\n console_log!(\"[example] scheduled run\");\n}\n```\n\nTest the core natively with an in-memory `Store`, and assert status codes, headers and bodies\nper route there. That is the contract a Rust server component carries. A Mzizi `service`\nstates the same facts as `example` and `ensure` clauses (below), but nothing lowers a Mzizi\nservice to a Worker, so a Rust Worker keeps its contract in its tests.\n\n### Project setup\n\n```bash\nnpm install -g wrangler\ncargo install worker-build\ncargo generate cloudflare/workers-rs\n```\n\n### wrangler.toml\n\n```toml\nname = \"mzizi-example-service\"\nmain = \"build/worker/shim.mjs\"\ncompatibility_date = \"2026-09-01\"\n\n[build]\ncommand = \"cargo install -q worker-build && worker-build --release\"\n\n[[kv_namespaces]]\nbinding = \"ITEMS\"\nid = \"<namespace id>\"\n\n[[triggers.crons]]\ncron = \"*/5 * * * *\"\n\n[[queues.consumers]]\nqueue = \"mzizi-example-signals\"\nmax_batch_size = 10\n```\n\nKeep `compatibility_date` current; a stale date silently pins you to old runtime behaviour.\nSecrets are set out of band with `wrangler secret put <NAME>` and never committed.\n\n### Storage\n\nA Mzizi service holds **no database client**. Its data is files bundled at build time (the way\n`api.mzizi.dev` and `mcp.mzizi.dev` serve the registry at a pinned commit) or Workers bindings:\nKV, R2, D1, Durable Objects. Put each behind a trait in the core so the core never imports\n`worker`. Only the Mzizi console (`app.mzizi.dev`, operated by Nyuchi) uses Supabase, and\nnothing else in the estate should talk to it.\n\n### Queue consumers\n\nA queue consumer needs the crate's `queue` feature:\n`worker = { version = \"0.8\", features = [\"queue\"] }`.\n\n```rust\n#[event(queue)]\npub async fn queue(batch: MessageBatch<String>, _env: Env, _ctx: Context) -> Result<()> {\n for message in batch.messages()? {\n // hand message.body() to the core, then acknowledge\n message.ack();\n }\n Ok(())\n}\n```\n\n## A Mzizi service (the language's backend slice)\n\nWrite one when the task is the language itself: a backend benchmark task, an example, or an\nexperiment with the slice. Do not use it for a live Mzizi service; nothing deploys it. The\nfull grammar, the runtime-owned behaviour and every `MZ08xx` code are in the `mzizi-language`\nskill (\"Services\"). A minimal service, which passes `mz check` and `mz contract`:\n\n```mz\n## Health and one item, with a JSON 404.\nservice items\n\n header \"x-service\" \"items\"\n\n record health\n field status: text\n end\n\n record problem\n field error: text\n end\n\n route health_check\n get \"/v1/health\"\n respond 200 json health status \"ok\"\n end\n\n route item\n get \"/v1/items/{name}\"\n when name in \"badge\" \"button\"\n respond 200 text \"{name}\"\n end\n respond 404 json problem error \"Not found\"\n end\n\n fallback\n respond 404 json problem error \"Not found\"\n end\n\n contract\n example get \"/v1/health\" body.status is \"ok\"\n example get \"/v1/items/card\" status is 404\n example options \"/v1/items/badge\" header \"allow\" is \"GET, HEAD, OPTIONS\"\n ensure header \"x-service\" is \"items\"\n end\n\nend service items\n```\n\nThe runtime answers `HEAD`, `OPTIONS`, `405` and the trailing-slash `308` itself, so a\nservice never writes them. The loop, with `mz` built from source (there is no release; see\n\"Getting `mz`\" in the `mzizi-language` skill):\n\n```bash\nmz check --agent items.mz # NDJSON; MZ08xx for routes and handlers\nmz fix items.mz # every exact fix\nmz contract --agent items.mz # runs the service in process; the summary adds contract_tested\nmz build items.mz --out ../target/mz-build/items\ncargo test --manifest-path ../target/mz-build/items/Cargo.toml # one test per example\nPORT=8787 cargo run --release --manifest-path ../target/mz-build/items/Cargo.toml\n```\n\nThe generated package depends on `axum` and `tokio` (pinned exactly), is its own workspace\nroot, and is never committed. Its runtime is one Rust function, the same routing algorithm\n`mz contract` evaluates.\n\n## An MCP server\n\n### Transport\n\nMCP over HTTP is **Streamable HTTP**, JSON-RPC 2.0:\n\n- `POST /mcp` — JSON-RPC requests\n- `GET /mcp` — SSE stream\n- `DELETE /mcp` — session cleanup\n- `OPTIONS /mcp` — CORS preflight\n\nA server exposes `tools/list` and `tools/call`, and optionally `resources/list` and\n`prompts/list`.\n\nUse `@modelcontextprotocol/sdk`. TypeScript is the right choice for an MCP server today,\nbecause the SDK, `hono` and `agents` are TypeScript-first; put any heavy logic in a Rust core\n(\"A Rust Worker\" above). Pin the SDK version deliberately: `agents` hard-pins\n`@modelcontextprotocol/sdk`, and letting your own declaration drift produces two incompatible\n`McpServer` classes in the type graph and a private-field mismatch at typecheck.\n\n### One factory, many entrypoints\n\nKeep the server definition free of any runtime binding, then wrap it per surface. This is what\nlets the same catalogue ship as a Worker, a generic fetch handler and a local stdio bin\n(`mzizi-mcp/src/`):\n\n```\nsrc/\n├── server.ts # createXMcpServer(deps) → McpServer ← the only place tools are defined\n├── http.ts # createXHttpHandler() — any fetch runtime\n├── worker.ts # Cloudflare Worker entry, bound to env\n└── stdio.ts # bin for local clients / the MCP Registry\n```\n\n### The catalogue is code; the data is bundled\n\n`mzizi-mcp` defines its tools in code (`src/tools.ts`) and reads its data from files bundled at\nbuild time: `scripts/generate-registry.mjs` fetches the registry at the exact commit in\n`registry.pin.json`, and `scripts/generate-skills.mjs` inlines `mzizi-skills/`. There is **no\ndatabase client, dependency, env var or secret** behind a read tool. To move the MCP to newer\nregistry content, change the pinned commit in a pull request; the diff is the review.\n\nThe gates are tests, not a live drift check:\n\n1. **A tool-surface suite** connects a real MCP `Client` to the factory over an in-memory\n transport and asserts the contract: the tool names and prefix, annotations, input and output\n schemas, and which tools need sign-in (`test/tool-surface.test.ts`, `free-tier.test.ts`).\n2. **A bundle suite** calls every tool against the real bundled data with the network disabled,\n and asserts that what a tool serves is byte-identical to the file it came from\n (`registry-bundle.test.ts`, `rust-first.test.ts`, `skills-bundle.test.ts`, `no-database.test.ts`).\n\n**Federating another MCP.** `mzizi-mcp` lists the docs MCP's tools under a `docs_` prefix and\nforwards each call, with a cached tool list and a clear tool error when the upstream fails\n(`src/docs-federation.ts`). Prefer that to copying another server's content.\n\n### Tool naming and annotations\n\nTools are `snake_case`, prefixed with the server (`mzizi_`), and named for the action:\n`mzizi_search`, `mzizi_get_component`, `mzizi_list_components`, `mzizi_get_tokens`,\n`mzizi_get_architecture`, `mzizi_get_doctrine`, `mzizi_get_skills`,\n`mzizi_check_accessibility`, `mzizi_report_issue`, `mzizi_fundi`, `mzizi_mcp_describe`. Read the\nlive list with `tools/list` rather than trusting a copy, this one included. Inputs take plain\nnames (`{ \"name\": \"button\" }`); the old `p_`-prefixed database parameters are gone.\n\nEvery tool carries a `title`, an `outputSchema`, and honest behaviour hints:\n\n- `readOnlyHint` — true only if the tool cannot mutate anything. An action that runs an audit\n or files a report is **not** read-only.\n- `destructiveHint` / `idempotentHint` / `openWorldHint` — set them deliberately; clients use\n them to decide what to auto-approve. A tool that reads only bundled data is\n `openWorldHint: false`.\n\nReturn `structuredContent` matching the declared `outputSchema`.\n\n### Static assets\n\nIf the Worker also serves `llms.txt`, `robots.txt` or `/.well-known/*` cards, put them behind an\n`[assets]` block so they resolve ahead of the OAuth handler; everything else falls through to\nthe server.\n\n### Instrumentation\n\nIf you report tool errors to an observability sink, do it fire-and-forget via\n`ctx.waitUntil`, so reporting never blocks or fails the response.\n\n### Publishing to npm and the MCP Registry\n\n`server.json` next to `package.json` is the source of truth, and `package.json#mcpName`\n**must** equal `server.json#name` or the registry refuses the publish. Registry names are\n`io.github.mzizi-dev/<server>`: the namespace is the GitHub owner of the repository the publish\nruns from, because `mcp-publisher login github-oidc` only authorises `io.github.<owner>/*`. The\nold `io.github.nyuchi/mzizi-mcp` entry is stale and no longer updated.\n\nRelease checklist:\n\n1. Bump the version everywhere it is stated: the constant the server reports in `serverInfo`\n (`MZIZI_MCP_VERSION` in `src/server.ts`), `package.json#version`, `server.json#version`,\n and `server.json#packages[0].version`. A mismatch here is the most common failed publish.\n2. Merge to `main`. `.github/workflows/publish-mzizi-mcp.yml` publishes to npm (skipped if the\n version is already there) and then runs `mcp-publisher login github-oidc` and\n `mcp-publisher publish`, treating \"this version already exists\" as success. OIDC means no\n registry secret. A merge that does not change the version is a no-op.\n\n## Access: free, except what acts as a person (every service)\n\nOwner decision, 2026-09-29: **the MCP server and the CLI are free, with no gate.** An anonymous\nsession can `initialize`, `tools/list` and call every tool except the Fundi ones. Gating starts\nat **Fundi**: `mzizi_fundi` and `mzizi_report_issue` act as a Mzizi console user.\n\nHow `mzizi-mcp` does it (`src/worker.ts`, `src/access.ts`):\n\n- A tokenless request to `/mcp` is routed around the OAuth provider, rate-limited per IP, and\n served with no user.\n- Any request with an `Authorization` header, and `/mcp/signin`, goes through\n `@cloudflare/workers-oauth-provider`, which validates the token and puts the WorkOS identity\n on `ctx.props`. A bad or expired token still gets the 401 that makes a client refresh.\n- The gated tools stay **listed** to an anonymous caller and answer with a tool error that\n starts `Sign-in required:` and says how to sign in. `access.ts` is the one list.\n\nWhen a person signs in, it is WorkOS Connect OAuth with PKCE (a public client, no secret). A\nmachine calling an internal service uses WorkOS M2M; only the Worker holds that secret, and it\nmints the token and proxies the call so a CLI never holds it. Auth happens **inline in the\nclient**, never by punting the user to a browser tab mid-task.\n\nValidate an inbound token against the WorkOS JWKS before any privileged work, and cache the\nJWKS; refetching per request is the classic latency bug. Return `401` for a missing or invalid\ntoken and `403` for a valid token without the required scope. Never collapse the two, or\ncallers cannot tell \"sign in\" from \"you can't do this\". Cron, queue and health routes are not\nuser-triggered and bypass the gate by design.\n\n## Logging (every service)\n\nPrefix every line with the service in brackets, as `mzizi-fundi` does with `[fundi]`, so it is\ngreppable across services:\n\n```rust\nconsole_log!(\"[example] cycle complete: {} records processed\", count);\nconsole_error!(\"[example] upstream error: {:?}\", e);\n```\n\nNever log a token, a secret, or a full request body.\n\n## Deploy (every service)\n\n```bash\nwrangler dev # local\nwrangler deploy # manual only; normally CI deploys\n```\n\nWire deploys to CI rather than a laptop, so what is live is what is merged. `mzizi-mcp` is\ndeployed by Cloudflare Workers Builds on every push to `main`, including its bundled registry\npin and skills. Run the test suites before you merge, and a live smoke pass of every read tool\n(`mzizi-mcp/scripts/integration.mjs`) after.",
32
38
  "source": "mzizi-dev/agent-tools/mzizi-skills/skills/mzizi-backend"
33
39
  },
34
40
  {
35
41
  "name": "discoverability",
36
- "description": "SEO + AIO for the Mzizi ecosystem: how to guarantee machines can find and render a site — social/messaging link previews (OpenGraph), search engines, and AI agents. Covers the 'if the machine can't see it, it doesn't exist' convention, the brand-agnostic Mzizi hive OG-image template (dynamic per page: title + description + site icon), build-time OG images on static Astro (the standard), the Next.js reference template in mzizi-registry, APCA-checked text colours, and a pre-ship checklist. Use when a shared link shows no preview or when adding a new route/site.",
37
- "body_mdx": "# Discoverability — SEO + AIO for the Mzizi ecosystem\n\n> **Covenant:** _If the machine can't see it, it doesn't exist._\n\nDiscoverability is the machine-facing surface of every Mzizi-ecosystem site — the\npart crawlers, social/messaging scrapers, and AI agents read. It is **cross-cutting**,\nnot a component you install: it is a genetic-code **convention** (\"every surface ships\na title, a description, a dynamic preview image, structured data, and an\nagent-readable index\") base-paired to the components that satisfy it (the OG template,\n`llms.txt`, JSON-LD helpers). A surface a machine can't parse may as well not exist —\nand a **missing social preview is the single biggest discoverability no-go**, because\nit's the first thing a human sees when a link is shared.\n\nAll values here are the current doctrine. The source of truth is the repo:\n`mzizi-dev/mzizi-registry` for doctrine and tokens, this package for the skills, served\nread-only at <https://api.mzizi.dev/v1> and by the MCP at `https://mcp.mzizi.dev/mcp`. There is\nno database behind any of it. Brand colour specifically is `palette.canonical.json` in this\npackage: 21 families, generated and gated, never retyped.\n\n## The number-one failure: a missing OG image\n\nThe most common — and most damaging — bug is a link that unfurls with **no preview\nimage at all**. The canonical cause is metadata that points at an image file that\ndoesn't exist:\n\n```ts\n// ❌ WRONG — points at a static file that isn't in /public, so every scraper 404s\nopenGraph: { images: [\"/og-image.png\"] }\ntwitter: { images: [\"/og-image.png\"] }\n```\n\nWorse, an explicit `images` array **overrides** a framework's dynamic OG route, so\nthe good image never gets a chance. **Rule: never hardcode `openGraph.images`.** Let\nthe framework's file-convention / endpoint serve a _generated_ image, and set an\nabsolute base URL so scrapers get an absolute URL.\n\n## The Mzizi \"hive\" OG card\n\nOne template, every route, every brand. The card is:\n\n- **Deep-night ground** (`#1B1A17`) with a faint flat-top **honeycomb hex lattice**\n (white at ~5% — texture, not noise) and a few solid **mineral accent cells**\n (cobalt / gold lead, plus tanzanite / malachite / copper for warmth).\n- The **site icon on top, above the title** (brand recognition first), then the\n title, an optional eyebrow, the SEO description, and the domain.\n- **Left-aligned** (reads better than centred at thumbnail size).\n- **Brand-agnostic:** the icon is a parameter, so the same template serves nyuchi\n (the bee), mukoko (the swarm), mzizi, shamwari — each with its own mark + domain.\n It picks up the _site's own icon_, never a hardcoded one.\n\n### Colours are APCA-checked\n\nOG text is read at thumbnail size, so contrast matters. Verified against the\n`#1B1A17` ground (APCA reverse polarity; design-system floor is **Lc 75 large /\nLc 90 body**):\n\n| Role | Hex | APCA Lc | Verdict |\n| -------------------------------- | --------- | ------- | ------- |\n| Title | `#F5F5F4` | 92 | ✓ body |\n| Secondary (eyebrow, description) | `#E5E3DE` | 81 | ✓ large |\n| Domain (gold) | `#FFD740` | 76 | ✓ large |\n\nDo **not** use dim greys like `#B2AFA8` (only Lc 50) for OG text — it looks fine on a\nmonitor and vanishes in a messaging thumbnail. Re-run APCA when changing any pair.\n\n## Implementation — Astro, static (the standard)\n\nNew surfaces are static Astro: mzizi.dev (`mzizi-dev/mzizi-site`) is one. A statically built\nsite generates its OG images **at build time**:\n\n1. A build-time image endpoint, `src/pages/og/[...slug].png.ts`, with `getStaticPaths()` over\n the site's pages or content collection. Render each page's title and description with\n **satori** to SVG, then rasterise with **sharp** to PNG.\n2. In the shared layout's `<head>`, set\n `<meta property=\"og:image\" content={new URL(`/og/${slug}.png`, Astro.site)} />` per page,\n plus `twitter:card` = `summary_large_image`. Set `site` in `astro.config.mjs` so every URL is\n absolute.\n3. Use the hive design: honeycomb, the site's icon, and the brand mineral.\n\nKeep the site's runtime dependency-light; satori and sharp are build-only. If the site renders\nMzizi Roots components, they arrive as static HTML from `dioxus-ssr` and change nothing here.\n\n**Docs on Mintlify** (docs.mzizi.dev) get their preview images from Mintlify. Check them with a\nscraper like any other surface; do not hand-wire `og:image` there.\n\n## Reference template — Next.js (App Router)\n\n`mzizi-registry` is a Next.js app and still carries the reference template at\n`lib/og/mzizi-og.tsx` (`renderMziziOg({ title, eyebrow, description, iconPath, domain })`),\nwired through `app/opengraph-image.tsx`. It no longer serves mzizi.dev. Do not start a new\nsurface on Next.js; use this only for a surface that is already on it:\n\n```tsx\n// app/opengraph-image.tsx (root) — and app/<section>/[slug]/opengraph-image.tsx\nimport { renderMziziOg, OG_SIZE, OG_CONTENT_TYPE } from \"@/lib/og/mzizi-og\"\nexport const runtime = \"nodejs\" // reads the icon PNG off disk\nexport const size = OG_SIZE\nexport const contentType = OG_CONTENT_TYPE\nexport default function Image() {\n return renderMziziOg({ title: \"Mzizi\" }) // per page: pass that page's title + description\n}\n```\n\n```ts\n// app/layout.tsx — set metadataBase, DO NOT set openGraph.images / twitter.images\nexport const metadata = {\n metadataBase: new URL(\"https://example.org\"),\n openGraph: { type: \"website\", siteName: \"Mzizi\", title: \"Mzizi\", description: \"…\" },\n twitter: { card: \"summary_large_image\", title: \"Mzizi\", description: \"…\" },\n // No `images` — the opengraph-image.tsx route supplies an absolute, hashed URL,\n // and the twitter card falls back to it.\n}\n```\n\n## Beyond the image — the rest of the convention\n\n- **Title + meta description** on every route (unique, page-specific).\n- **Structured data**: schema.org JSON-LD for the page's primary entity.\n- **`llms.txt`** at the site root — the machine-readable index for LLMs.\n- **`robots.txt` + `sitemap.xml`** — generated, not hand-maintained.\n- **Canonical URLs** absolute (a set `metadataBase` / `site`).\n- **Agent-readiness**: an MCP discovery card at `/.well-known/mcp.json` pointing at the MCP\n endpoint (mzizi.dev ships one for `https://mcp.mzizi.dev/mcp`).\n\n## Pre-ship checklist\n\n- [ ] Sharing the URL in Slack / WhatsApp / iMessage / X renders a preview **image**.\n- [ ] The OG image is **generated** (dynamic), carries the page's title + description + the site icon, and its URL is **absolute**.\n- [ ] No hardcoded `openGraph.images` / `twitter.images`.\n- [ ] OG text colours pass APCA (Lc ≥ 75 large / ≥ 90 body).\n- [ ] `metadataBase` / `site` is set; canonical URL is absolute.\n- [ ] Title + meta description are unique per route.\n- [ ] `llms.txt`, `robots.txt`, `sitemap.xml` present; JSON-LD on primary entities.\n- [ ] Validate the card in a scraper (e.g. opengraph.xyz) after deploy.\n\n_The whole helix is Mzizi — and discoverability is the convention that makes each\nsurface legible to the machines that carry it. Ndiri nekuti tiri._",
42
+ "category": "build",
43
+ "description": "SEO + AIO for the Mzizi ecosystem: how to guarantee machines can find and render a site — social/messaging link previews (OpenGraph), search engines, and AI agents. Covers the 'if the machine can't see it, it doesn't exist' convention, the brand-agnostic Mzizi hive OG-image template (dynamic per page: title + description + site icon), build-time OG images on static Astro (the standard), the rules for a surface already on Next.js (next >= 16.3.6, no request input in an ImageResponse), APCA-checked text colours, and a pre-ship checklist. Use when a shared link shows no preview or when adding a new route/site.",
44
+ "body_mdx": "# Discoverability — SEO + AIO for the Mzizi ecosystem\n\n> **Covenant:** _If the machine can't see it, it doesn't exist._\n\nDiscoverability is the machine-facing surface of every Mzizi-ecosystem site — the\npart crawlers, social/messaging scrapers, and AI agents read. It is **cross-cutting**,\nnot a component you install: it is a genetic-code **convention** (\"every surface ships\na title, a description, a dynamic preview image, structured data, and an\nagent-readable index\") base-paired to the components that satisfy it (the OG template,\n`llms.txt`, JSON-LD helpers). A surface a machine can't parse may as well not exist —\nand a **missing social preview is the single biggest discoverability no-go**, because\nit's the first thing a human sees when a link is shared.\n\nAll values here are the current doctrine. The source of truth is the repo:\n`mzizi-dev/mzizi-registry` for doctrine and tokens, this package for the skills, served\nread-only at <https://api.mzizi.dev/v1> and by the MCP at `https://mcp.mzizi.dev/mcp`. There is\nno database behind any of it. Brand colour specifically is `palette.canonical.json` in this\npackage: 21 families, generated and gated, never retyped.\n\n## The number-one failure: a missing OG image\n\nThe most common — and most damaging — bug is a link that unfurls with **no preview\nimage at all**. The canonical cause is metadata that points at an image file that\ndoesn't exist:\n\n```ts\n// ❌ WRONG — points at a static file that isn't in /public, so every scraper 404s\nopenGraph: { images: [\"/og-image.png\"] }\ntwitter: { images: [\"/og-image.png\"] }\n```\n\nWorse, an explicit `images` array **overrides** a framework's dynamic OG route, so\nthe good image never gets a chance. **Rule: never hardcode `openGraph.images`.** Let\nthe framework's file-convention / endpoint serve a _generated_ image, and set an\nabsolute base URL so scrapers get an absolute URL.\n\n## The Mzizi \"hive\" OG card\n\nOne template, every route, every brand. The card is:\n\n- **Deep-night ground** (`#1B1A17`) with a faint flat-top **honeycomb hex lattice**\n (white at ~5% — texture, not noise) and a few solid **mineral accent cells**\n (cobalt / gold lead, plus tanzanite / malachite / copper for warmth).\n- The **site icon on top, above the title** (brand recognition first), then the\n title, an optional eyebrow, the SEO description, and the domain.\n- **Left-aligned** (reads better than centred at thumbnail size).\n- **Brand-agnostic:** the icon is a parameter, so the same template serves nyuchi\n (the bee), mukoko (the swarm), mzizi, shamwari — each with its own mark + domain.\n It picks up the _site's own icon_, never a hardcoded one.\n\n### Colours are APCA-checked\n\nOG text is read at thumbnail size, so contrast matters. Verified against the\n`#1B1A17` ground (APCA reverse polarity; design-system floor is **Lc 75 large /\nLc 90 body**):\n\n| Role | Hex | APCA Lc | Verdict |\n| -------------------------------- | --------- | ------- | ------- |\n| Title | `#F5F5F4` | 92 | ✓ body |\n| Secondary (eyebrow, description) | `#E5E3DE` | 81 | ✓ large |\n| Domain (gold) | `#FFD740` | 76 | ✓ large |\n\nDo **not** use dim greys like `#B2AFA8` (only Lc 50) for OG text — it looks fine on a\nmonitor and vanishes in a messaging thumbnail. Re-run APCA when changing any pair.\n\n## Implementation — Astro, static (the standard)\n\nNew surfaces are static Astro: mzizi.dev (`mzizi-dev/mzizi-site`) is one. A statically built\nsite generates its OG images **at build time**:\n\n1. A build-time image endpoint, `src/pages/og/[...slug].png.ts`, with `getStaticPaths()` over\n the site's pages or content collection. Render each page's title and description with\n **satori** to SVG, then rasterise with **sharp** to PNG.\n2. In the shared layout's `<head>`, set\n `<meta property=\"og:image\" content={new URL(`/og/${slug}.png`, Astro.site)} />` per page,\n plus `twitter:card` = `summary_large_image`. Set `site` in `astro.config.mjs` so every URL is\n absolute.\n3. Use the hive design: honeycomb, the site's icon, and the brand's colour (for mzizi.dev,\n hematite, a Heritage tone; see the constellation in `mzizi-design`).\n\nKeep the site's runtime dependency-light; satori and sharp are build-only. If the site renders\nMzizi Roots components, they arrive as static HTML from `dioxus-ssr` and change nothing here.\n\n**Docs on Mintlify** (docs.mzizi.dev) get their preview images from Mintlify. Check them with a\nscraper like any other surface; do not hand-wire `og:image` there.\n\n## A surface already on Next.js (App Router)\n\nDo not start a new surface on Next.js; the standard is the static Astro build above. There is\nno Next.js reference template to copy any more: `mzizi-registry` is not a Next.js app, and its\n`next/og` template (`lib/og/`) and OG routes were removed on 2026-10-02 over a critical RCE\nexposure (GHSA-vcvr-r3jv-pc5j, `next` >= 16.2.0 < 16.3.6): a route put the URL slug into a\nNode `ImageResponse`. Build the hive card from the design above. For a surface that is already\non Next.js:\n\n- **Require `next` >= 16.3.6** (the patched line). Check the lockfile, not only the range.\n- **Never put request input into an `ImageResponse`.** No route param, slug, search param,\n header or cookie goes into the image, as text or as a path. Look the slug up in the site's\n own content and render only that entry's own title and description; an unknown slug is a\n 404 (`notFound()`), never a fallback that echoes it.\n- **Generate the images at build time.** Give each dynamic segment's `opengraph-image.tsx`\n `generateStaticParams()` over the site's own content and `export const dynamicParams = false`,\n so no OG image renders per request and an unknown path is a 404.\n- **Set `metadataBase` and never `openGraph.images` / `twitter.images`** in the layout's\n `metadata`. The `opengraph-image.tsx` file convention supplies an absolute, hashed URL, and\n the Twitter card falls back to it.\n\n## Beyond the image — the rest of the convention\n\n- **Title + meta description** on every route (unique, page-specific).\n- **Structured data**: schema.org JSON-LD for the page's primary entity.\n- **`llms.txt`** at the site root — the machine-readable index for LLMs.\n- **`robots.txt` + `sitemap.xml`** — generated, not hand-maintained.\n- **Canonical URLs** absolute (a set `metadataBase` / `site`).\n- **Agent-readiness**: an MCP discovery card at `/.well-known/mcp.json` pointing at the MCP\n endpoint (mzizi.dev ships one for `https://mcp.mzizi.dev/mcp`).\n\n## Pre-ship checklist\n\n- [ ] Sharing the URL in Slack / WhatsApp / iMessage / X renders a preview **image**.\n- [ ] The OG image is **generated** (dynamic), carries the page's title + description + the site icon, and its URL is **absolute**.\n- [ ] No hardcoded `openGraph.images` / `twitter.images`.\n- [ ] OG text colours pass APCA (Lc ≥ 75 large / ≥ 90 body).\n- [ ] `metadataBase` / `site` is set; canonical URL is absolute.\n- [ ] Title + meta description are unique per route.\n- [ ] `llms.txt`, `robots.txt`, `sitemap.xml` present; JSON-LD on primary entities.\n- [ ] Validate the card in a scraper (e.g. opengraph.xyz) after deploy.\n\n_The whole helix is Mzizi — and discoverability is the convention that makes each\nsurface legible to the machines that carry it. Ndiri nekuti tiri._",
38
45
  "source": "mzizi-dev/agent-tools/mzizi-skills/skills/discoverability"
46
+ },
47
+ {
48
+ "name": "digital-hygiene",
49
+ "category": "dev",
50
+ "description": "Disk hygiene for agents, after a temp directory filled a host with 217 GB of leftover clones: check free disk before starting and refuse to fan out below 50 GB; clone only under $TMPDIR in a named folder per task; share build caches (CARGO_TARGET_DIR, the pnpm store, the npm cache); never commit build output; on close, audit uncommitted changes, stashes and branches on no remote, map each branch to its PR state (a squash merge means 'not on a remote' proves nothing), publish or report what is left, then delete the clone; a periodic sweep that prunes node_modules, target, .astro, .wrangler, .turbo and .next older than N minutes and deletes clean clones; the safety rules (no rm -rf on an unchecked variable, never ~/GitHub or another session's folders, never staging, report what was freed); and hygiene.sh, a portable dry-run-by-default script for check, audit, prune, close and sweep.",
51
+ "body_mdx": "# Digital hygiene: leave the machine as you found it\n\n> **Covenant:** _A clone is borrowed space. Give it back when the work is done._\n\nOn 2026-10-05 an agent host ran out of disk. Its temp directory held **217 GB** in about\n**930 leftover agent clones**: roughly 92 GB of `node_modules` and 14 GB of Rust `target/`.\nNo agent had removed its clone when its work closed. When the disk filled, every agent on the\nmachine stopped mid-task, including the ones doing nothing wrong. Hygiene is not tidiness. It\nis what lets the next agent run at all, and Ubuntu applies here: what one agent leaves behind,\nevery other agent pays for.\n\nThis skill has four parts: what to do **before** you start, **while** you work, **on close**,\nand in the **periodic sweep**. The safety rules apply to all of them. The script `hygiene.sh`\nin this skill folder runs the checks, the audit and the prune for you.\n\n## Get the script\n\n`hygiene.sh` is portable bash (3.2+) for macOS and Linux. Every command is a **dry run**\nunless you pass `--apply`.\n\n| Where you are | Path |\n| ------------------------------- | ------------------------------------------------------------------------------------------------------------- |\n| `@nyuchi/mzizi-skills` from npm | `node_modules/@nyuchi/mzizi-skills/skills/digital-hygiene/hygiene.sh` |\n| The public `mzizi` plugin | `skills/digital-hygiene/hygiene.sh` in the plugin |\n| Only the MCP | `curl -fsSL https://unpkg.com/@nyuchi/mzizi-skills/skills/digital-hygiene/hygiene.sh -o \"$TMPDIR/hygiene.sh\"` |\n\n```bash\nH=\"$TMPDIR/hygiene.sh\" # or one of the paths above\nbash \"$H\" check # free disk; exits 1 below 50 GB\nbash \"$H\" audit # every clone under $TMPDIR: CLEAN, KEEP or ACTIVE\nbash \"$H\" prune # regenerable build folders it would remove\nbash \"$H\" close \"$TMPDIR/<task>/<repo>\" --apply # close your own clone\nbash \"$H\" sweep --exclude \"$TMPDIR/<your-task>\" # prune + delete clean idle clones (dry run)\n```\n\nOptions: `--root DIR` (default `$HYGIENE_ROOT`, else `$TMPDIR`), `--older-than MIN` (default\n120), `--min-gb N` (default 50), `--exclude PATH` (repeatable), `--apply`. `HYGIENE_GH=none`\nturns off PR lookups. Exit codes: `0` ok, `1` low disk or a refused path, `2` a clone was kept\nbecause it holds unpublished work.\n\n## Before you start\n\n1. **Check the disk.** Run `df -h /`, or `hygiene.sh check`. **Below 50 GB free, do not fan\n out.** Don't spawn sub-agents and don't start parallel clones or builds. Run a sweep first\n (see below), or report the low disk to your supervisor. A fan-out on a nearly full disk\n fails all at once, part-way through every branch.\n2. **Clone only under `$TMPDIR`, one named folder per task.** Use a fixed, readable name, for\n example `\"$TMPDIR/digital-hygiene/agent-tools\"`. Never clone into `~/GitHub`, which holds the\n owner's working copies, and never clone into a random or nameless folder. A named folder is\n what lets you, the sweep and the next session tell whose work it is.\n\n ```bash\n mkdir -p \"$TMPDIR/<task-name>\" && git clone https://github.com/<org>/<repo>.git \"$TMPDIR/<task-name>/<repo>\"\n ```\n\n3. **Share build caches; don't multiply them.** Each clone with its own caches costs gigabytes\n in every clone.\n - **Rust:** one `CARGO_TARGET_DIR` per machine or session, for example\n `export CARGO_TARGET_DIR=\"$TMPDIR/cargo-target\"`. Every clone then reuses one compiled\n dependency tree instead of building its own `target/`.\n - **pnpm:** the content-addressed store is shared by default. Keep it that way: never pass\n `--store-dir` into the clone, and don't use `node-linker=hoisted` copies when links will\n do.\n - **npm:** the cache (`~/.npm`) is shared. Use `npm ci --prefer-offline` and don't point\n `--cache` into the clone.\n - Install only what the task needs. `pnpm install --filter <pkg>...` installs one workspace\n package; a whole monorepo install for a docs fix is waste.\n\n## While you work\n\n- **Don't commit build output.** `node_modules/`, `target/`, `dist/`, `.astro/`, `.wrangler/`,\n `.turbo/`, `.next/` and `.vercel/output/` belong in `.gitignore`. If one shows up in\n `git status`, ignore it; don't add it.\n- **Delete what you made and no longer need.** That includes scratch builds, downloaded\n tarballs and one-off test clones, as soon as they have served their purpose.\n- **Check the disk again before anything large:** a release build, a full test matrix, or a\n second fan-out.\n\n## On close: when the PR is merged or abandoned\n\nClosing a clone is an audit first and a deletion second. **Never delete a clone you have not\naudited.** The audit asks one question: is there any work here that exists nowhere else?\n\n1. **Fetch**, so you judge against the remote as it is now: `git fetch --prune origin`.\n2. **Uncommitted changes:** `git status --porcelain`. Anything listed exists only here.\n3. **Stashes:** `git stash list`. A stash is never on a remote.\n4. **Local branches whose commits are on no remote:**\n\n ```bash\n git for-each-ref --format='%(refname:short)' refs/heads | while read -r b; do\n n=$(git rev-list --count \"$b\" --not --remotes)\n [ \"$n\" -gt 0 ] && echo \"$b: $n commit(s) on no remote\"\n done\n ```\n\n5. **Map each of those branches to its PR state.** Merged or closed counts as published:\n\n ```bash\n gh pr list -R <owner>/<repo> --head <branch> --state all --json number,state,title\n ```\n\n **A squash merge makes \"not on any remote\" meaningless as proof.** Squashing writes a new\n commit on the base branch, so the branch's own commits stay local-only even though the\n work shipped. Then the remote branch is deleted, and every squash-merged branch looks\n unpublished. So check in this order:\n - Is every commit on a remote? Then it is published.\n - Is there a PR from this branch that is `MERGED` or `CLOSED`? Then it is published (merged,\n or deliberately abandoned).\n - Does every unpublished commit's subject appear on a remote branch, as is or with the\n squash suffix `(#123)` appended? Then it is published.\n - Otherwise it is **unpublished**. This includes an `OPEN` PR whose branch is behind your\n local commits.\n\n6. **Publish or report anything genuinely unpublished.** Push the branch and open a PR (into\n `staging` where the repo has one), or report it to your supervisor with the branch name,\n the commit subjects and why it was not published. Don't delete work to make a clone\n \"clean\".\n7. **Delete the clone.** Delete it only after steps 1 to 6 come back clean, and then report\n what was freed.\n\n`hygiene.sh close <clone> --apply` runs steps 1 to 5 and 7. A clone nested inside yours,\nsuch as a gitignored cache clone, is deleted with it, so it is audited too: unpublished work\nthere keeps the whole clone. It exits `2`, and deletes\nnothing, when step 6 is needed.\n\n## The periodic sweep\n\nRun a sweep when `check` fails, before a large fan-out, and at the end of a long supervisor\nsession. It has two passes, and the dry run comes first:\n\n1. **Safe prune of regenerable folders.** These are `node_modules`, `target`, `.astro`,\n `.wrangler`, `.turbo` and `.next`, removed **only when older than N minutes** (default 120),\n so an agent mid-build is not hit. A sibling manifest has to prove what each folder is:\n `Cargo.toml` (or the cargo `CACHEDIR.TAG`) for `target`, `package.json` for `.next`,\n `.turbo` and `.astro`, and a `wrangler` config for `.wrangler`. A folder named `target` that\n holds notes is not a build cache. Pruning loses nothing, because the next install or build\n recreates the folder.\n2. **Audit report of the clones, then delete the clean ones.** Each clone gets one verdict:\n - `CLEAN`: nothing unpublished, idle. Deleted with `--apply`.\n - `KEEP`: unpublished work, with the reason. Never deleted: publish it or report it.\n - `ACTIVE`: a file changed inside the window. Another session is probably using it. Leave\n it.\n\n```bash\nbash hygiene.sh sweep --exclude \"$TMPDIR/<your-task>\" # read the report\nbash hygiene.sh sweep --exclude \"$TMPDIR/<your-task>\" --apply # then act on it\n```\n\nReport the result: what was freed, how many clones were deleted, and every `KEEP` with its\nreason.\n\n## Safety rules (all of the above)\n\n- **Never `rm -rf` a path built from an unchecked variable.** If `$DIR` is empty or unset,\n `rm -rf \"$DIR/\"*` deletes from `/`. Use fixed base paths and let `find … -prune` produce the\n paths. Check every path before you delete it: it is absolute, it is inside the root, it is not\n the root itself, `/` or `$HOME`, and it is not under `~/GitHub`. Then use\n `rm -rf -- \"$path\"`. `hygiene.sh` routes every deletion through that check.\n- **Never touch `~/GitHub` working copies, or another session's active folders, without\n asking.** The owner keeps live work in `~/GitHub`. The script refuses a root or a clone\n there. An `ACTIVE` clone, or one you did not create, is someone else's until they or your\n supervisor say otherwise. Use `--exclude` for your own live task folder.\n- **Never delete `staging`,** neither the branch on the remote nor a release PR's head branch.\n A clone is deleted locally; branches on the remote are the repository's business, not the\n sweep's.\n- **Dry run first, then `--apply`.** Read what would go before anything goes.\n- **Report what was freed,** in GB, with the number of folders and clones and every clone that\n was kept and why. \"Cleaned up\" is not a report.\n\n## Checklist\n\n- [ ] `df -h /` before starting; 50 GB free or more before any fan-out.\n- [ ] Cloned under `$TMPDIR/<task-name>/`, never `~/GitHub`.\n- [ ] `CARGO_TARGET_DIR` shared; pnpm store and npm cache not redirected into the clone.\n- [ ] No build output committed.\n- [ ] On close: fetched, then checked the status, the stashes, and the branches on no remote.\n PR state looked up per branch, because of squash merges.\n- [ ] Unpublished work pushed into a PR, or reported.\n- [ ] Clone deleted, and the amount freed reported.",
52
+ "source": "mzizi-dev/agent-tools/mzizi-skills/skills/digital-hygiene"
53
+ },
54
+ {
55
+ "name": "progress-report",
56
+ "category": "dev",
57
+ "description": "All dev work runs on a 10-minute progress-report loop. When dev work starts, schedule a report every 10 minutes (Claude Code: /loop 10m or CronCreate */10 * * * *, which dies with the session and expires after 7 days; without cron, report at each natural checkpoint). Measure, don't guess: 20-cell progress bars with a percentage and a one-line basis (plan steps from the plan doc or tracking issue, features done, code migrated as lines in the new tree against the old, and an Overall bar labelled estimate when it is not exact); per workstream, open PRs, the latest release, staging ahead of main, CI, a live HTTP check for deployed sites and published-vs-draft counts for content; one 'Since last report:' line; and 'Needs you:' as the last line (owner actions and decisions, or nothing). One line when nothing changed. Ticks are read-only: never publish, release, merge, delete or deploy from a tick without the owner's approval of that specific action. No secrets or PII. The loop stops when the work is done (a final report with the outcome) or when the owner says stop. Ships progress.sh, a portable read-only script: bar, pct, repo, lines, http.",
58
+ "body_mdx": "# Progress report: measured, every ten minutes\n\n> **Covenant:** _The owner should never have to ask \"where are we?\"_\n\nAll dev work runs on a 10-minute progress-report loop. The owner reads the reports to decide\nwhat to do next, so every number in them is measured, and every report ends with what the\nowner has to do. A report that guesses is worse than none, because it is believed.\n\nThis skill has four parts: **starting** the loop, **what a report holds**, **the rules for a\ntick**, and **stopping**. The script `progress.sh` in this skill folder renders the bars and\nreads the repo state for you.\n\n## Get the script\n\n`progress.sh` is portable bash (3.2+) for macOS and Linux. It is **read-only**: it reads git,\nthe GitHub API (through `gh`) and HTTP status codes, and never writes anything.\n\n| Where you are | Path |\n| ------------------------------- | ----------------------------------------------------------------------------------------------------------------------- |\n| `@nyuchi/mzizi-skills` from npm | `node_modules/@nyuchi/mzizi-skills/skills/progress-report/progress.sh` |\n| The public `mzizi` plugin | `skills/progress-report/progress.sh` in the plugin |\n| Only the MCP | `curl -fsSL https://unpkg.com/@nyuchi/mzizi-skills/skills/progress-report/progress.sh -o \"${TMPDIR:-/tmp}/progress.sh\"` |\n\n```bash\nP=\"${TMPDIR:-/tmp}/progress.sh\" # or one of the paths above\nbash \"$P\" bar 78 \"Plan steps\" \"7 of 9\" # Plan steps ████████████████░░░░ 78% 7 of 9\nbash \"$P\" pct 7 9 # 78\nbash \"$P\" repo mzizi-dev/agent-tools # open PRs, latest release, staging vs main, CI\nbash \"$P\" -C \"${TMPDIR:-/tmp}/task/app\" lines origin/staging app/ # lines under app/ at that ref\nbash \"$P\" http https://mcp.mzizi.dev/ https://mzizi.dev # status code per deployed site\n```\n\n`lines` counts the committed tree at a ref (`git grep -c ''`, binary files skipped), never the\nworking copy, so two reports on the same commit give the same number. `repo` reads only, every\npage of each list. Its CI line takes the newest run of each GitHub Actions workflow, the\nchecks of other apps, and the commit statuses (deploy integrations often post only a status)\non the default branch's head, and names what failed. A workflow whose newest run was\ncancelled is shown as cancelled (`; 1 cancelled: CI`), neither green nor red, because nothing\nfinished it; read that as \"not verified\", never as passing. `pct` and\n`bar` never round unfinished work up to 100%.\n\n## Starting the loop\n\nWhen dev work starts, schedule the report before you write any code:\n\n- **Claude Code:** `/loop 10m <report prompt>`, or CronCreate with `*/10 * * * *`. The cron\n lives in the session: it dies when the session ends and expires after 7 days, so a long build\n re-arms it in each new session.\n- **Surfaces without cron** (a chat, a CI job, an agent with no scheduler): report at each\n natural checkpoint instead, such as a PR opened, CI finished, a merge, a release, or a\n blocker.\n\nThe report prompt names the work and where its measures come from, for example:\n\n```text\nProgress report for the Toddle rebuild (nyuchi/toddle-enhancement-extension#41).\nBars: plan steps from the checklist in #41; features from the feature table in docs/plan.md;\ncode migrated = lines in app/ vs src/ at origin/staging. Workstreams: the repo, the Chrome Web\nStore listing. Use the progress-report skill. Read-only.\n```\n\nWrite the measures down once, in the tracking issue (see \"Track big work in GitHub issues\").\nThen every tick measures the same thing the same way.\n\n## What a report holds\n\n### 1. Progress bars\n\nEach bar is 20 cells of `█` and `░`, a percentage, and a **one-line basis**: what was counted.\nUse `progress.sh bar` so every bar has the same width and alignment. Typical bars:\n\n| Bar | Basis (measured) |\n| ------------- | ------------------------------------------------------------------------------------ |\n| Plan steps | Steps done of total, from the repo's plan doc or the tracking issue's checklist. |\n| Features done | Features shipped of the features listed in the plan. |\n| Code migrated | Lines in the new tree against the old (`progress.sh lines`), at the same ref. |\n| Overall | How it combines the others. Labelled `(estimate)` and shown as `~N%` when not exact. |\n\nOnly show a bar you can measure. If there is no checklist, say so and don't invent a\ndenominator. A bar never reads 100% for unfinished work: `progress.sh` keeps a cell empty\nuntil the count is complete.\n\n### 2. Per workstream\n\nFor each repo, service or content stream the work touches:\n\n- **Open PRs**, the **latest release or tag**, **staging ahead of main**, and **CI** on the\n default branch: `progress.sh repo <owner/repo>`.\n- **Deployed sites:** a live HTTP check (`progress.sh http <url>…`). Report the status code\n you got; don't call a site live without one.\n- **Content**, when the work includes it (Intercom articles, Sanity documents, store\n listings): the **published** count against the **draft** count, read from the source's API\n or MCP.\n\n### 3. Since last report\n\nOne line: what changed since the previous tick (a PR merged, CI went green, a bar moved from\n68% to 74%).\n\n### 4. Needs you\n\nThe **last line** of every report: `Needs you:` and the owner actions and decisions that block\nthe work, each one specific enough to act on. Write `Needs you: nothing` when nothing does.\n\n### When nothing changed\n\nIf nothing changed since the last tick, the whole report is one line, such as\n`10:40 no change since 10:30 (PR #212 CI still running). Needs you: nothing.`\n\n## Example\n\nThe Toddle rebuild format, partway through (the numbers are illustrative):\n\n```text\nProgress report 14:20 (Toddle rebuild, nyuchi/toddle-enhancement-extension#41)\n\nFeatures rebuilt (UI) ████████████████████ 100% every visible feature is React in app/\nPlan steps ████████████████░░░░ 78% 7 of 9\nCode in app/ ██████████████░░░░░░ 69% 16,090 app/ · 7,266 src/\n───────────────────────────────────────────────\nOverall (estimate) ███████████████░░░░░ ~75% plan steps, weighted by code moved\n\nnyuchi/toddle-enhancement-extension\n open PRs 2 (#58 settings panel, #59 remove src/)\n latest release v1.6.0 (2026-10-05)\n staging vs main 4 ahead, 0 behind\n CI on main green (9 checks)\nChrome Web Store 1.6.0 published, 1.7.0 draft staged\n\nSince last report: #57 merged; code in app/ 64% → 69%.\nNeeds you: approve the 1.7.0 store submission (staged, not submitted).\n```\n\n## Rules for a tick\n\n- **Ticks are read-only by default.** Never publish, release, merge, delete or deploy from a\n report tick unless the owner already approved that specific action. A report observes the\n work; it does not do it. Installing an approved test build is fine when the owner set that\n up.\n- **Measure, don't guess.** Every number comes from a command, an API or a file you read in\n this tick. If a read fails, say `?` and why, rather than reusing an old number.\n- **Privacy.** No secrets, tokens or credential status, and no personal information (names of\n members of the public, emails, phone numbers) in a report. Name a secret by its name, never\n its value.\n- **Short.** A report is read on a phone between other things. Bars, workstreams, one \"Since\n last report\" line, then \"Needs you\".\n\n## Stopping\n\nThe loop stops when:\n\n- **the work is done:** send one final report with the outcome (what merged, what is live and\n verified, what is left) and its \"Needs you\" line, then cancel the loop; or\n- **the owner says stop.**\n\nCancel the scheduled job (`CronDelete`, or end the `/loop`) so nothing keeps reporting on\nfinished work.\n\n## Checklist\n\n- [ ] The loop is scheduled (10 minutes) or, without cron, checkpoints are named.\n- [ ] Each bar has a measured basis; the Overall bar says `estimate` when it is one.\n- [ ] Each workstream shows PRs, release, staging vs main, CI, live HTTP, and content counts.\n- [ ] One \"Since last report:\" line, and \"Needs you:\" is the last line.\n- [ ] Nothing published, released, merged, deleted or deployed by a tick without approval.\n- [ ] No secrets, no PII.\n- [ ] A final report when the work is done, and the loop cancelled.",
59
+ "source": "mzizi-dev/agent-tools/mzizi-skills/skills/progress-report"
39
60
  }
40
61
  ];
41
62
  /** Bundle version, from mzizi-skills/index.json. */
42
- export const SKILLS_VERSION = "0.8.1";
63
+ export const SKILLS_VERSION = "0.10.0";
43
64
  //# sourceMappingURL=skills.js.map