| name | yard-documentation |
| description | General YARD documentation rules and workflow for all Ruby source code. Use when writing or reviewing YARD doc comments, generating missing docs, updating examples, fixing doc errors, or checking documentation coverage. |
YARD Documentation
General YARD documentation rules and workflow for all Ruby source code.
Contents
How to use this skill
Attach this file to your Copilot Chat context, then invoke it with the source
files that need YARD updates. Use it when adding new APIs, fixing doc warnings,
or improving existing YARD quality and examples.
Related skills
Documentation Standards
ruby-git uses YARD for API documentation:
All classes, modules, constants, attributes, and methods must have YARD
documentation. Methods with Ruby private visibility require a short description
and all applicable tags from the Methods rules — @param, @return,
@raise, @yield/@yieldparam/@yieldreturn, and @overload — with the
exception that @example may be omitted unless an example materially clarifies
the behavior. Private methods still need YARD docs for developer reference in
source, even though YARD excludes them from generated HTML by default.
YARD Formatting Rules
Doc comments are rendered as markdown via the redcarpet gem. Write all
free-text descriptions, tag values, and examples using markdown syntax. These rules
apply to all documentation regardless of element type:
Named length limits
Three named limits govern line and description length throughout this document:
LINE_LIMIT (90 characters) — the preferred maximum length of any
physical YARD comment line, measured from column 1 and including every
character: indentation, #, tag metadata, and all text. Wrap prose at
this limit wherever possible.
LINE_MAX (120 characters) — the hard ceiling for lines that cannot
be wrapped without breaking their meaning. The following content may
exceed LINE_LIMIT up to LINE_MAX; it must not exceed LINE_MAX:
- URLs — in
@see tags or markdown links; a URL cannot be split
- Long inline code spans — a
`backtick` span whose content
alone approaches or exceeds LINE_LIMIT
- Long
[Type] expressions — a type such as
[String, Pathname, Array<String, Pathname>] that fills the tag
metadata column before any description text begins
@example code lines — real code inside an example block that
cannot be reflowed without changing its meaning
- Markdown table rows — pipe-delimited table rows that cannot be
split across lines
SUMMARY_LIMIT (90 characters) — the maximum length of a tag's
description text, measured by concatenating the description from the first
tag line with all immediately following indented continuation lines
(stripping the leading # from each and joining with a single space).
Applies to the description text only — not the tag name, [Type], option
key, or (default).
Doc comment placement
YARD doc comments must appear immediately above the element they document (class,
module, method, constant, or attribute) with no intervening blank lines or
non-comment code.
Blank lines around tags
Every individual YARD tag must be preceded by a blank comment line (#) unless it is the very first line of a doc comment.
A YARD tag is any comment token matching @!?[a-z_]+ — that is, @word (regular
tags such as @param, @return, @raise, @api, @abstract, @deprecated,
etc.) or @!word (directives such as @!attribute, @!method, @!scope, etc.).
Within the tag block there are no other exceptions: consecutive same-kind tags (e.g.
multiple @param lines) each require their own preceding blank line.
Never use raw blank lines inside a doc comment block
A raw blank line — an empty line with no leading # — terminates the YARD doc
comment block at that point. Any comment lines that follow the raw blank line are
treated as separate, unattached comments and will not appear in the generated
documentation. Always use a blank comment line (#) to separate paragraphs or
continuation text within a YARD block:
Correct — blank comment line keeps the block intact:
Incorrect — raw blank line silently drops the alias note:
Watch for editors that auto-strip trailing spaces from # lines, silently
creating raw blank lines.
Short descriptions
The short description (the first sentence of any doc comment, or the inline text of
a @param, @return, @raise, etc. tag) must:
- Be a single sentence
- Not end with sentence-ending punctuation (
., ?, !)
- Element-level short descriptions (on classes, modules, and methods) start
with an uppercase letter (e.g.
Returns the commit count,
Represents a Git branch)
- Tag short descriptions (
@option, @param, @return, @raise, @yield,
@yieldparam, etc.) all start with a lowercase letter (e.g. @option options [Boolean, nil] :force (nil) overwrite existing files, @param name [String] the branch name, @return [String] the result, @raise [ArgumentError] when no name is provided)
For tags, the summary text is the description that follows the tag
metadata (tag name, [Type], option key, and (default)). For example, in:
@option options [Boolean, nil] :ignore_case (nil) ignore case distinctions
the summary text is ignore case distinctions.
Tag line and summary length
Every physical YARD doc line should not exceed LINE_LIMIT. When a tag's
description would push a line past LINE_LIMIT, split it at a word boundary
onto a continuation line indented two extra spaces. For content that cannot
be wrapped (URLs, long inline code spans, long [Type] expressions,
@example code lines, markdown table rows), lines may extend up to
LINE_MAX but must not exceed it.
Additionally, the concatenated description — the description text from the
first tag line joined with all continuation lines — must not exceed
SUMMARY_LIMIT. If the concatenated description exceeds SUMMARY_LIMIT,
shorten it and move the excess detail into a paragraph after a blank # line.
For example, this tag has a description of 84 characters (within
SUMMARY_LIMIT), but the single physical line is 102 characters (exceeds
LINE_LIMIT) and must be split:
Split so each physical line fits within LINE_LIMIT:
If the tag metadata itself is long (e.g. a long [Type] or @option key),
start the description on an indented continuation line so only the metadata
appears on the first physical line.
If more explanation is needed, add continuation paragraphs after a blank
comment line (#). Every physical line — in the summary and in any
continuation paragraph — must independently fit within LINE_LIMIT
(or LINE_MAX for unwrappable content such as URLs, long inline code
spans, long [Type] expressions, @example code, or table rows).
These rules apply equally to tag text (@param, @return, etc.) — the first
sentence of a tag is its short description. The no-punctuation rule applies only to
short descriptions; continuation paragraphs use normal prose punctuation (periods).
Separate continuation paragraphs with a blank comment line.
Correct — tag title without punctuation, blank line before continuation:
Incorrect — trailing period on title, missing blank line before continuation, and @return concatenated summary exceeds SUMMARY_LIMIT (132 chars):
@return must always include a type
Every @return tag must include a [Type] specifier. @return the value is
incorrect; write @return [Object] the value (or a more specific type). If the
return value is the block's return value, use @return [Object].
No shell calls in @example blocks
Never use backtick shell calls (`true`, `git version`) or process-status
globals ($?, $CHILD_STATUS) in @example blocks. They are side-effecting,
environment-dependent, and confuse readers about the type of object being
demonstrated. Construct example objects directly in Ruby instead:
Incorrect:
Correct:
Blank lines within @example blocks
Within @example blocks, blank comment lines (#) render as literal blank lines in
the displayed code. Use them for readability between setup and assertions, but be
aware they are literal content, not tag separators.
@example titles are required
Every @example tag must include a title — the descriptive text on the same line
after @example. Write @example Basic usage, not bare @example. Titles appear
as headings in generated docs and help readers scan multiple examples.
Cross-reference links only resolve to objects included in generated docs
YARD renders {ClassName#method} as a hyperlink only when the target method is
included in the generated documentation. Public objects are included by default,
and objects marked with @api private remain included with a private annotation.
Ruby private methods are excluded by default. Do not write
{Git::Lib#some_private_method} — it will render as plain text and may generate
an unresolved reference warning.
If you need to refer to a private method, describe it in prose instead, or link to
the public method that callers should use.
Inline code formatting
Use backtick code spans for inline code (`true`, `nil`, symbols, type
names, method calls). Do not use the RDoc +value+ style; it is inconsistent with
the project's markdown rendering via redcarpet.
Escaping { in descriptions
YARD treats { as the start of a cross-reference link. Because redcarpet consumes
one \ before YARD sees it, write \\{ (two backslashes) to produce a literal
{ — redcarpet reduces \\ to \, leaving \{ for YARD. For example, use
'stash@\\{0}' to render as stash@{0}. Using only \{ still triggers a YARD
unresolved link warning.
Cross-reference links
Link to other code objects anywhere in a doc comment using {ClassName},
{ClassName#method}, {#method_in_same_class}, or {Class::CONSTANT}. An
optional title follows the reference separated by a space:
{Git::Base#log the log method}. Do not use brace syntax inside @see tags —
@see links automatically without braces. @see accepts three target forms:
- Code objects:
@see Git::Base#log
- URLs:
@see https://git-scm.com/docs/git-log
- Quoted text:
@see "Pro Git, Chapter 2"
Type specifier conventions
The [Types] field in @param, @return, @raise, etc. supports:
- Plain types:
[String], [Integer], [Git::Base]
- Multiple types:
[String, nil], [String, Array<String>]
- Parametrized types:
[Array<String>], [Hash<Symbol, String>]
- Duck-types (responds to):
[#read], [#to_s]
[Boolean] — conventional meta-type for true or false (not a real Ruby class)
[void] — for @return tags on methods whose return value must not be used
@api private vs @private
Use @api private (not @private) to mark internal classes and modules. @api private includes the object in generated docs with a private annotation; YARD's
@private tag excludes the object from docs entirely.
@since — do not use
Do not add @since tags. The project has no historical @since annotations, and
retroactively tagging existing APIs is impractical at v4.x. Version introduction
history is tracked through git blame and the CHANGELOG instead.
@todo — do not use
Do not add @todo tags. Track incomplete work in GitHub Issues, not in source
comments. YARD renders @todo prominently in generated docs, and these annotations
go stale quickly.
@abstract
Use @abstract on classes or methods that must be subclassed or overridden before
use. Include guidance text describing what the subclass must implement:
@abstract Subclass and implement {#run}. Do not use @abstract on concrete
classes or fully implemented methods.
Element-Specific Rules
Classes
- One-sentence class description as a noun phrase (or starting with "Represents…") — the description should pass
the "This class is a…" litmus test (i.e. you should be able to prefix "This class
is a" and produce a grammatical sentence). "Represents…" is an accepted convention.
Do not start descriptions with "This class is…", "Provides…", or "Encapsulates…".
- Good:
Wrapper around the git binary, Immutable value object for a branch delete result, Represents a git branch
- Bad:
This class wraps the git binary, Provides branch deletion,
Encapsulates branch state
@api public or @api private tag to declare visibility — use @api public for
stable user-facing classes; use @api private for internal implementation classes
(e.g., Git::Lib, Git::Commands::*, parsers)
- At least one
@example showing typical instantiation or primary usage — this
applies to all classes including @api private classes
- Error/exception classes must also state when the error is raised in class-level
prose, using caller-facing wording such as
Raised when branch deletion fails
- Deprecated classes must include
@deprecated explaining the migration path
- When present, class-level tags must appear in this order:
@example, @note,
@deprecated, @see, @api, @abstract
Modules
- One-sentence description — "Namespace for…", "Provides helpers for…", or
"Mixin that adds…"
@api public or @api private — use @api public for stable user-facing modules;
use @api private for internal implementation modules
- No
@example required unless the module provides standalone methods
- Deprecated modules must include
@deprecated explaining the migration path
- When present, module-level tags must appear in the same order as class-level
tags:
@example, @note, @deprecated, @see, @api
Constants
- A comment immediately above the constant describing its purpose and valid values
- No special YARD tag is needed; YARD picks up the preceding comment automatically
- Add
# @return [Type] when the constant holds a collection, frozen structure, or
domain-specific type whose shape is not immediately obvious from the value
Attributes
- For explicitly written
attr_reader, attr_accessor, and attr_writer declarations,
place the documentation directly above the attribute; do not use the @!attribute
directive
- For dynamically created attributes,
Data.define members, or Struct.new
members, you must use a # @!attribute [r/rw/w] name YARD directive
- Must include
@return [Type] description explaining the value and its units or
constraints if relevant
- For explicit
attr_reader/attr_accessor/attr_writer, include a short
description paragraph above the attribute in addition to @return
- For
@!attribute directives (in Data.define / Struct.new), the @return
tag inside the directive block serves as the sole documentation — no separate
short description is needed
- Tags inside
@!attribute (and @!method) directive blocks must be indented
two extra spaces relative to the directive itself
- Must be defined at the class level, not inside method bodies
Dynamically defined methods (@!method)
Use the # @!method name(params) directive for methods created via
metaprogramming (define_method, method-generating DSLs, etc.) that have no
literal def. Place the directive and its doc comment where the method would
logically appear in the class body. Tags inside the directive block follow the
same indentation rule as @!attribute — indented two extra spaces relative to
the directive.
Data.define classes
Immutable value objects defined with Data.define use the following conventions
(see Git::BranchDeleteFailure for a canonical example):
- Class-level doc: noun-phrase short description,
@example, @see, @api
- One
@!attribute [r] directive per member with @return [Type] description
- Attribute directives are placed after class-level tags and before the
Data.define line
- Custom methods defined inside the
Data.define block follow standard method
rules
Struct.new classes
Document Struct.new classes using the same conventions as Data.define classes:
class-level doc with a noun-phrase short description, @example, @see, @api,
and one @!attribute directive per member with @return [Type] description.
BranchDeleteFailure = Data.define(:name, :result)
Methods
- All methods must have a short description that:
- Starts with a verb (
Returns, Resets, Finds — not "The…" or "This method…")
- Omits the subject — write "Returns the commit count", not "This method returns
the commit count"
- States the outcome, not the mechanism —
Finds the nearest tagged ancestor not
Iterates through commits checking tags
- Mentions key parameters inline —
Resets HEAD to the given ref rather than
relying solely on param tags
- Avoids restating the method name — add specificity about what kind, from where,
or what is returned
- Is specific about return values —
Returns true if the branch exists, false otherwise beats Returns a Boolean
- Omits implementation details — callers don't care about internal loops or temp
variables
- Methods use these standard YARD tags:
@param for each method parameter, in signature order; omit @param entirely
on zero-argument methods
@return on every method; use [void] when the return value must not be used
@raise for each caller-relevant exception the method can raise as part of its
contract; omit @raise when the method has no documented exceptional path
- Methods without Ruby
private visibility must have one or more @examples;
Ruby-private methods may omit @example unless usage would otherwise be unclear
- Methods that yield to a block must include
@yield [param_names], one
@yieldparam name [Type] per yielded parameter, and @yieldreturn [Type]; omit
all yield tags on methods that do not yield
- Use
@overload when a method has distinct call signatures with different
parameters or return types — each overload gets its own full set of tags.
Methods that yield only when an optional block is given should use @overload
to document the with-block and without-block signatures separately
- Methods whose signature uses an anonymous keyword splat (
**),
anonymous positional splat (*), or the argument forwarding
parameter (...) must document their call shapes with @overload blocks
that name the parameters. @param, @option, @yield, and @yieldparam
cannot bind to an anonymous splat or to ... — the named overload signature
is what gives YARD a parameter to attach the docs to. Do not introduce a
named splat (or expand ... into *args, **kwargs, &block) solely so the
tags will bind; that conflicts with RuboCop's Style/ArgumentsForwarding
cop, and the @overload form satisfies both
- Use
@note for callouts that need visual emphasis: thread-safety warnings,
significant side effects, or platform-specific behaviour
- Deprecated methods must include
@deprecated explaining the migration path,
e.g. @deprecated Use {#new_method} instead
@api is optional on methods — when omitted, the method inherits the containing
class's @api level. Use it only when the method's intended visibility differs
from the class's level (e.g. an @api private helper inside an @api public
class)
Step 1: Identify What Needs Documentation
bundle exec yard stats --list-undoc
bundle exec yard doc lib/git/base.rb --no-output
Step 2: Write Documentation
Follow the YARD documentation templates below. Use the standard template when a
method has a single call signature. Use the overload template when a method has
distinct call signatures with different parameters or return types.
When @overload blocks are present:
- Keep
@param, @option, and @return inside overload blocks only
- Keep overload-specific
@raise inside the relevant overload block
- Keep shared
@raise at top level only once (do not duplicate inside overloads)
- Keep non-signature tags (
@note, @deprecated, @see, @api) at top level
Avoid duplicating the same @raise at both top level and overload level. This
causes conflicting or noisy generated docs.
Trigger: always use @overload when the signature contains *, **, or ...
Anonymous splats and the forwarding parameter give @param, @option, @yield,
and @yieldparam no named parameter to bind to. YARD silently drops or
mis-renders these tags when the signature is, for example, def foo(**) or
def foo(paths = '.', **) — even though paths is named, the keyword options
have no name so every @option is unbound. As soon as any parameter in the
signature is anonymous (*, **, or ...), switch to @overload for the entire
signature (see Documenting anonymous splats with @overload).
Standard template (no @overload)
When present, tags must appear in the order shown. @param tags appear in
parameter order; @option tags appear immediately after the @param for the hash
they describe. Every @option tag must be preceded by a @param for the
options hash, and all @option tags under that @param must reference the same
parameter name. For keyword arguments (**options or **kwargs), use
@param options [Hash] (or the actual splat name) as the preceding @param:
def method_name(name, options = {})
end
Overload template
Each @overload block carries its own @example, @param, @option, @return,
@raise, @yield, @yieldparam, and @yieldreturn tags. Tags that are
not call-signature-specific — @note, @deprecated, @see, @api — remain
at the top level:
def method_name(name, options = {})
end
Overload decision matrix
Use this matrix to decide whether to use @overload and where to place tags:
| Method signature or behavior | Documentation form |
|---|
Single named signature, no */**/... | Standard template (no @overload) |
Uses anonymous *, **, or ... | @overload required |
| Multiple call shapes (different params and/or return types) | One @overload per shape |
| Shared errors across all call shapes | Top-level @raise once |
| Error only for specific call shape | @raise only in that overload |
For overload docs, keep @param/@option/@return in overload blocks. Use
top-level @raise only for errors that apply to every overload.
Documenting anonymous splats with @overload
When the method signature uses an anonymous splat — def foo(*), def foo(**),
def foo(*, **) — or the argument forwarding parameter def foo(...) —
@param, @option, @yield, and @yieldparam tags have no parameter name
to bind to. RuboCop's Style/ArgumentsForwarding cop prefers these forms when
arguments are forwarded unchanged, so naming the splat (or expanding ...
into *args, **kwargs, &block) is not an acceptable workaround. Use
@overload blocks that introduce named parameters for documentation purposes
only:
def add(paths = '.', **)
Git::Commands::Add.new(@execution_context).call(*Array(paths), **).stdout
end
The same approach applies to .... The overload signature names the
parameters; the actual def keeps ... so RuboCop is satisfied:
def run(command, ...)
Git::Commands::Run.new(@execution_context).call(command, ...)
end
When a method has multiple genuinely distinct call shapes, write one
@overload block per shape as in the Overload template
above.
Anonymous block parameter (&) is not covered by this rule.
@yield, @yieldparam, and @yieldreturn describe what is yielded to the
block, not the block parameter itself, so they bind correctly even with an
anonymous &. Use a named block parameter (&block) and a @param block [Proc] tag only in the rare case where the block is documented as a
first-class Proc value (stored, returned, or passed elsewhere) rather than
yielded to.
Step 3: Verify Documentation
bundle exec yard doc
open doc/index.html
bundle exec yard doc 2>&1 | grep -i "warn"
Verify @example code runs correctly in bundle exec bin/console.
Check that all @see references point to valid targets.
Review checklist — tag line length
For every @param, @return, @raise, @option, @yield, @yieldparam,
and @yieldreturn tag, check both limits:
LINE_LIMIT: Count every character from column 1 (indentation, #,
metadata, text) on each physical line. If any wrappable line exceeds
LINE_LIMIT, split at a word boundary onto a continuation line (indented
two extra spaces). Apply this check to every continuation line
independently. Lines containing URLs, long inline code spans, long [Type]
expressions, @example code, or markdown table rows may extend up to
LINE_MAX.
SUMMARY_LIMIT: Strip # from each continuation line and join
with a single space. If the concatenated description exceeds
SUMMARY_LIMIT, shorten it and move the excess into a paragraph after
a blank # line.
Command Reference
bundle exec yard doc
bundle exec yard server --reload
bundle exec yard stats
bundle exec yard stats --list-undoc
bundle exec yard doc lib/git/base.rb
bundle exec yard doc --no-output 2>&1
bundle exec yard ri Git::Base