with one click
docs-mdoc-conventions
// Shared reference for mdoc code block modifiers and Docusaurus admonitions used across ZIO library documentation skills. Include when writing any documentation that contains Scala code blocks.
// Shared reference for mdoc code block modifiers and Docusaurus admonitions used across ZIO library documentation skills. Include when writing any documentation that contains Scala code blocks.
Stop and consult this skill whenever your response would involve any fact or code related to ZIO HTTP. Covers: installation and setup, routing (Routes, RoutePattern, PathCodec), handlers and HandlerAspect, the declarative Endpoint API and HttpCodec, client and server configuration, middleware, request/response/headers/cookies, WebSockets, Server-Sent Events, Body and binary codecs, form data, template DSL, OpenAPI documentation and code generation, authentication (basic, digest, bearer, JWT, OAuth, WebAuthn), TLS/SSL/mTLS, testing, ZIO Config integration, Datastar/HTMX integration, migration, and any ZIO HTTP library versions or dependencies. Trigger this even for coding tasks that import zio.http, content that mentions ZIO HTTP features or types, or comparisons involving ZIO HTTP. Any time you would otherwise rely on memory for ZIO HTTP details, verify here instead — your training data may be outdated or wrong.
Stop and consult this skill whenever your response would involve any fact or code related to ZIO core or the ZIO ecosystem. Covers: ZIO effects and type aliases (ZIO, Task, UIO, UEffect), fibers and fiber management, concurrency primitives (Hub, Queue, Ref, Semaphore), Software Transactional Memory (STM), ZIO Streams (ZStream, ZSink, ZPipeline, ZChannel), ZIO Test framework and test utilities, ZLayer and dependency injection patterns, error management and error types, scheduling and retries, resource management and scoping, ZIO Config, ZIO Schema, ZIO JSON, ZIO Kafka, and all official ZIO libraries and integrations. Trigger this for any ZIO coding task, type signatures, library features, architectural patterns, or comparisons involving ZIO. Any time you would otherwise rely on memory for ZIO details, verify here instead — your training data may be outdated or wrong.
Scans merged GitHub PRs (from latest commit back to an upstream base ref) and produces a documentation-coverage audit report. Processes 20 PRs per batch and asks before continuing to the next batch. Skips PRs already checked in previous runs using a persistent state file. For each new PR, determines whether documentation is required based on labels, changed files, and content signals, then checks whether docs exist and grades coverage using four rubric levels: Well Documented, Partially Documented, Stub, or Not Documented. Outputs a focused report showing only PRs that require docs, sorted by coverage gap severity. Use this skill whenever the user wants to know which merged PRs are missing documentation, wants a documentation debt audit, or asks "what needs to be documented?", "which PRs have no docs?", or similar coverage questions. Invoke it even if the user just says "doc audit" or "show me undocumented changes."
Critique and fix existing documentation files using an automatic review loop. Spawns critics to review docs and makers to fix issues. Iterates until approved or max 3 rounds. Handles single files or directories. Works with available tools.
Prose style rules for documentation (reference pages, how-to guides, tutorials). Use this skill whenever writing or editing documentation to ensure consistency, clarity, and professionalism across all docs.
Shared integration checklist for new ZIO library documentation pages. Include after writing any new reference page or how-to guide to ensure it is wired into the site navigation.
| name | docs-mdoc-conventions |
| description | Shared reference for mdoc code block modifiers and Docusaurus admonitions used across ZIO library documentation skills. Include when writing any documentation that contains Scala code blocks. |
| allowed_tools | Read, Glob, Grep |
| color | silver |
Phase 1 — Planning only, no edits yet Scan the document and identify every Scala code block missing an mdoc modifier (skip pseudocode / type-signature-only blocks). For each block found:
sbt mdoc --in <file>.md and confirm zero errors"Do not touch any source file until the full task tree is created and you have listed it for confirmation.
Phase 2 — Execution, one leaf task at a time Work through the tree top-to-bottom. For each parent task:
in_progressin_progress → apply modifier → mark child 1 completedin_progress → run sbt mdoc → confirm exit 0 → mark child 2 completedcompleted
Only then move to the next parent task.Phase 3 — Mechanical validation
After all tasks are completed, run:
bash ${CLAUDE_PLUGIN_ROOT}/skills/docs-mdoc-conventions/check-mdoc-conventions.sh <file.md>
Verify exit code is 0. If not, re-open the relevant tasks and fix.
Each modifier has a specific role. Choose based on whether you need scope sharing and whether output should render:
mdoc:compile-only — Renders source code only, isolated scope (no definitions carry over).
This is the default for self-contained examples where you want to show the structure but not
the evaluated output. Each block compiles alone; subsequent blocks cannot reference definitions
from a compile-only block.
mdoc:silent — Renders nothing (hidden), scope shared with subsequent blocks.
Use to define types, values, or imports that later blocks will reference. Scope persists until
you use silent:reset. You cannot redefine the same name in a later block — use
mdoc:silent:nest for that.
mdoc:silent:nest — Renders nothing, scope shared, code wrapped in anonymous object.
Like silent, but allows you to shadow/redefine names from earlier blocks (e.g., redefining
Person with different fields in a later section). Use when silent would fail due to name collision.
mdoc:silent:reset — Renders nothing, clears all prior scope.
Wipes the entire accumulated scope and starts fresh. Use when switching to a completely different
context (new domain, new imports) mid-document and silent:nest wouldn't suffice.
mdoc (no qualifier) — Renders source + evaluated output, scope shared with subsequent blocks.
Shows both the code and its REPL-style result (as if you'd written // Right(42L) by hand, but
evaluated). Can build on definitions from prior silent/silent:nest blocks, or run standalone
for self-contained examples that just need to show their output.
mdoc:invisible — Invisible code block, scope shared with subsequent blocks.
Signals "hidden imports only" — rare in practice. Prefer including imports directly in a
mdoc:silent setup block (so they're visible in scope) or inside a compile-only block
(for self-contained examples). Use invisible only when you need imports shared across blocks
but must not appear anywhere in the rendered output.
No mdoc (plain ```scala) — Renders source code only, not compiled.
Use for pseudocode, ASCII diagrams, type signatures for illustration, or non-Scala syntax (e.g., sbt configuration).
-Never hardcode expression output in comments: Let mdoc render output automatically, don't add comments like // None or // "hello". Use bare mdoc to show all vals; only use mdoc:silent when output is verbose boilerplate.
Bad vs. Good:
val x = 42 // 42val x = 42 (mdoc renders the output)Use this decision tree to pick the right modifier:
Is this real executable Scala code?
│
├─ NO → Use plain ```scala (pseudocode, ASCII art, type signatures)
│
└─ YES → Do later blocks need these definitions?
│
├─ NO → Do you want to show the output/result?
│ │
│ ├─ NO → Use mdoc:compile-only (source only, isolated)
│ │
│ └─ YES → Use mdoc (source + output, isolated)
│
└─ YES → Is this a later block showing a result?
│
├─ YES → Use mdoc (source + output)
│
└─ NO → Are you redefining a name from an earlier block?
│
├─ YES → Use mdoc:silent:nest (shadow existing names)
│
└─ NO → Use mdoc:silent (regular setup)
After any mdoc:silent block, if you later need a completely different context (new domain, new imports), use mdoc:silent:reset to clear all state.
import zio.blocks.schema._
case class Product(
name: String,
price: Double,
category: String,
inStock: Boolean,
rating: Int
)
object Product extends CompanionOptics[Product] {
implicit val schema: Schema[Product] = Schema.derived
val price: Lens[Product, Double] = optic(_.price)
}
def columnName(optic: zio.blocks.schema.Optic[?, ?]): String = {
val nodes = optic.toDynamic.nodes
nodes.collect { case f: DynamicOptic.Node.Field => f.name }.mkString("_")
}
Now with columnName in scope, we can call it and see the result:
columnName(Product.price)
columnName(Product.name)
This pair shows the two-block pattern: silent for setup (which doesn't render), then mdoc for expressions where the output is meaningful to show.
When you need to redefine a name (e.g., Person), use nest modifier:
Block A: mdoc
├─ case class Person(...) ← in scope
└─ val alice = ... ← in scope
Block B: mdoc
├─ Can reference alice ✓
└─ Can reference Person ✓
Block C: mdoc:nest
├─ All prior scope accessible ✓
└─ Can redefine Person ✓
Block D: mdoc
├─ Can reference new Person ✓
└─ Cannot reference old Person ✗
case class User(name: String, age: Int)
val user = User("Alice", 30)
Each compile-only block stands alone. The next example in the document doesn't have access to person.
Only use this pattern when the first block defines multiple larger setup code (e.g., multiple case classes, imports) that later blocks will reference and the later block should be evaluated to show output.
def add(a: Int, b: Int): Int = a + b
Now call it and show the result:
add(2, 3)
If the setup is just a single line or two, it's often cleaner to combine it with the output block:
def add(a: Int, b: Int): Int = a + b
add(2, 3)
mdoc:silent (case classes, imports)mdoc (show output)mdoc (reuse prior definitions)mdoc:silent:reset + mdoc:silent (fresh context)mdoc:compile-only (standalone)When a document contains many independent, self-contained examples (e.g., each type in a library having 4–6 usage examples), every example's first code block must use mdoc:silent:reset to prevent variable name collisions across examples.
Without reset, variables like val file, val config, or val user defined in Example 1 conflict with identically-named vars in Example 2—producing "Conflicting definitions" errors even when the examples are conceptually separate.
import zio.blocks.codegen.ir._
import zio.blocks.codegen.emit._
val user = CaseClass("User", List(Field("id", TypeRef.Long)))
val file = ScalaFile(
packageDecl = PackageDecl("com.example"),
types = List(user)
)
ScalaEmitter.emit(file, EmitterConfig())
The next example resets again:
val order = CaseClass("Order", List(Field("id", TypeRef.Long)))
val file = ScalaFile(
packageDecl = PackageDecl("com.example"),
types = List(order)
)
ScalaEmitter.emit(file, EmitterConfig())
Rule: In multi-example documents, :reset is not "sparingly used"—it is used once per independent example. The "use sparingly" advice in the Tips section applies to within a single example (where :nest is usually better).
:reset### section is a fresh, self-contained example that reuses common variable names like file, config, or user// result comments — use mdoc to show real outputsbt docs before committing mdoc blockssilent block if possible:reset at the right scope — once per independent example in multi-example documents; prefer :nest for minor redefinitions within a single exampleBefore submitting changes, run the mdoc-conventions checker against the file. It flags any plain ```scala block that should carry an mdoc modifier:
bash ${CLAUDE_PLUGIN_ROOT}/skills/docs-mdoc-conventions/check-mdoc-conventions.sh <file.md>
Run with --help for full usage. Exit codes:
| Code | Meaning |
|---|---|
0 | No violations — every executable Scala block has an mdoc modifier. |
1 | One or more code blocks are missing mdoc modifiers. |
2 | Invocation error (missing/extra arguments, file not found). |
If mdoc produces more than 3 compilation errors, the blocks are likely not properly isolated. Check for missing :reset or :nest modifiers, or name collisions between blocks. If the issue persists, strip all mdoc modifiers from the reported lines, confirm the errors are gone, then re-apply the correct modifiers one by one using the decision tree — running sbt mdoc --in <path>.md after each change to verify before continuing.
Common mistakes when writing mdoc blocks? See references/troubleshooting.md for solutions to.
When a section shows syntax that differs between Scala 2 and Scala 3, use Docusaurus tabs instead of sequential prose blocks. This lets readers pick their version once and have all tab groups on the page sync together.
Add these two lines at the top of any .md file that uses tabs (right after the closing
--- of the frontmatter, before any prose):
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
<Tabs groupId="scala-version" defaultValue="scala2">
<TabItem value="scala2" label="Scala 2">
```scala mdoc:compile-only
// Scala 2 syntax here
```
</TabItem>
<TabItem value="scala3" label="Scala 3">
```scala mdoc:compile-only
// Scala 3 syntax here
```
</TabItem>
</Tabs>
groupId="scala-version" — this syncs all tab groups on the page when the
reader picks a version.defaultValue="scala2" — Scala 2 is shown first by default.<TabItem> are required for mdoc to process fenced code blocks
correctly.mdoc:compile-only is the correct modifier for code inside tabs (same as everywhere
else).scala mdoc:* blocks are
rewritten.Use Docusaurus admonition syntax for callouts: (Titles are optional)
:::note[Title of the note]
Additional context or clarification.
:::
:::tip[Title of the tip]
Helpful shortcut or best practice.
:::
:::warning[Title of the warning]
Common mistake or gotcha to avoid.
:::
:::info[Title of the info]
Background information that is useful but not essential.
:::
:::danger[Title of the danger]
Serious risk of data loss, incorrect behavior, or security issue.
:::
Use admonitions sparingly — at most 3–4 in a typical document. They should highlight genuinely important information, not decorate every section.