Problem Statement
docvet defaults to ignore_init = True, which is correct — most style guides recommend documenting constructor parameters in the class docstring, not in __init__. But this creates a gap: if __init__ is ignored for presence, and the class docstring has no Args: section, then constructor parameters go entirely undocumented.
Current Behavior
A class can have an __init__ with multiple parameters and no documentation of those parameters anywhere — no finding produced.
class Connection:
"""A database connection.
Attributes:
host: The database hostname.
port: The connection port.
"""
def __init__(self, host: str, port: int, timeout: float = 30.0):
self.host = host
self.port = port
self._timeout = timeout
This class documents host and port as attributes but never documents timeout as a constructor parameter. A user reading the docs doesn't know timeout exists or what it defaults to.
Proposed Solution
Check: undocumented-init-params
Either:
- The class docstring has an
Args:/Parameters: section, OR
__init__ itself has a docstring with an Args: section
At least one must exist when __init__ takes parameters beyond self.
What it doesn't check
This rule checks that constructor parameters are documented somewhere. It's the constructor-specific companion to the presence check.
Example
class Worker:
"""A background worker."""
def __init__(self, name: str, max_retries: int = 3):
self.name = name
self._max_retries = max_retries
Finding: undocumented-init-params: Class 'Worker' __init__ takes parameters (name, max_retries) but neither the class docstring nor __init__ has an Args section
Fixed:
class Worker:
"""A background worker.
Args:
name: The worker's identifier.
max_retries: Maximum retry attempts before giving up.
"""
def __init__(self, name: str, max_retries: int = 3):
self.name = name
self._max_retries = max_retries
Implementation sketch
def _check_undocumented_init_params(symbol, sections, node_index, config, file_path) -> Finding | None:
if symbol.kind != "class":
return None
node = node_index.get(symbol.line)
if not isinstance(node, ast.ClassDef):
return None
init_node = _find_init(node)
if init_node is None:
return None
# Check if __init__ has non-self parameters
args = init_node.args
all_params = args.posonlyargs + args.args + args.kwonlyargs
non_self = [a for a in all_params if a.arg not in ("self", "cls")]
if not non_self:
return None
# Check class docstring for Args/Parameters section
if "Args" in sections or "Parameters" in sections:
return None
# Check __init__ docstring for Args section
init_docstring = ast.get_docstring(init_node, clean=False)
if init_docstring:
init_sections = _parse_sections(init_docstring)
if "Args" in init_sections or "Parameters" in init_sections:
return None
param_names = ", ".join(a.arg for a in non_self)
return Finding(
file=file_path, line=symbol.line, symbol=symbol.name,
rule="undocumented-init-params",
message=f"Class '{symbol.name}' __init__ takes parameters ({param_names}) "
f"but neither the class docstring nor __init__ has an Args section",
category="recommended",
)
Configuration
[tool.docvet.enrichment]
require-init-param-docs = true # default
Acceptance Criteria
Technical Notes
Files changed: enrichment.py (~40 lines), config.py (~3 lines), tests (~50 lines)
Category: recommended — some classes intentionally have minimal public surface and document via type annotations only.
Sphinx/RST interaction: Check for :param name: / :type name: fields in either docstring.
Interaction with presence.ignore_init: Specifically designed to complement ignore_init = True. When ignore_init = False, the user already requires __init__ docstrings and param agreement would cover the content.
BMAD Workflow
When ready to implement:
/bmad-bmm-quick-spec -> /bmad-bmm-quick-dev
Problem Statement
docvet defaults to
ignore_init = True, which is correct — most style guides recommend documenting constructor parameters in the class docstring, not in__init__. But this creates a gap: if__init__is ignored for presence, and the class docstring has noArgs:section, then constructor parameters go entirely undocumented.Current Behavior
A class can have an
__init__with multiple parameters and no documentation of those parameters anywhere — no finding produced.This class documents
hostandportas attributes but never documentstimeoutas a constructor parameter. A user reading the docs doesn't knowtimeoutexists or what it defaults to.Proposed Solution
Check:
undocumented-init-paramsEither:
Args:/Parameters:section, OR__init__itself has a docstring with anArgs:sectionAt least one must exist when
__init__takes parameters beyondself.What it doesn't check
This rule checks that constructor parameters are documented somewhere. It's the constructor-specific companion to the presence check.
Example
Finding:
undocumented-init-params: Class 'Worker'__init__takes parameters (name, max_retries) but neither the class docstring nor__init__has an Args sectionFixed:
Implementation sketch
Configuration
Acceptance Criteria
__init__but noArgs:section anywhere produce a findingArgs:in class docstring satisfies the checkArgs:in__init__docstring satisfies the checkselfin__init__produce no finding__init__produce no findingrequire-init-param-docsaddedTechnical Notes
Files changed: enrichment.py (~40 lines), config.py (~3 lines), tests (~50 lines)
Category:
recommended— some classes intentionally have minimal public surface and document via type annotations only.Sphinx/RST interaction: Check for
:param name:/:type name:fields in either docstring.Interaction with
presence.ignore_init: Specifically designed to complementignore_init = True. Whenignore_init = False, the user already requires__init__docstrings and param agreement would cover the content.BMAD Workflow
When ready to implement:
/bmad-bmm-quick-spec->/bmad-bmm-quick-dev