You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The docstrings have grown into changelogs and design notes. This was not one careless change: the style guide asked for it, and every agent followed it literally.
Measured on origin/master (3c11c909):
v1.0.3
v1.1.0
today
_cpp.py total lines
405
1705
1842
of which docstring
221 (55%)
787
879 (48%)
CPP.__init__ docstring
36 lines
102
117
Package-wide (public modules, _backend excluded): 95 docstrings are >= 60 lines, 29 are >= 100. The longest:
lines
symbol
239
CPPPlot.feature_map
214
CPP.run
187
CPPPlot.heatmap
177
EmbeddingPreprocessor.fetch_embeddings
176
CPP.run_num
174
AAPredPlot.predict_group
Two distinct causes, both traceable:
1. Version history is rendered into the API pages.CPP.run opens with "Added in version 0.1.0" followed by three stacked "Changed in version 1.1.0" blocks, so a reader meets the method's history before its behaviour. Package-wide: 242 versionadded + 40 versionchanged, 11 of the latter in _cpp.py alone. Origin: commit a4537eed (2026-06-01), a deliberate backfill pass, following docs/source/index/docstring_guide.rst, which said ".. versionchanged:: X.Y.Z when behaviour changes" and offered the parameter-level form only as an alternative.
2. Each feature wrote its rationale into the constructor. Every block in CPP.__init__ traces to the commit that added the feature: bootstrap (b6e53f9f), bootstrap_kws key-by-key (4d514213), ci plus the "Uncertainty per feature" note (41d87157), n_batches (6407b52a), n_sample_batches (c8cb7f73), the splits auto-cap note (4030c9f9), the "intrinsically binary" multi-class note (bd64516b), the _kws preamble (5451c3eb). Each is defensible alone; together they are a design document in a constructor docstring.
A related drift, same root cause, already corrected in a branch: CONTEXT.md and docs/source/index/glossary.rst described the part vocabulary as "TMD-centric" and "semantically wrong", prescribing a rename, while the published Feature Identification chapter has always said Target Middle Domain. Three independent planning passes each designed a CPP region-naming layer from that text alone.
Goal
Docstrings say what an object does today, in the house voice; history lives in CHANGELOG.md and the release notes; rationale lives in the user guide and protocols. Bring the worst offenders back to that, with the maintainer deciding what is genuinely reference material and what is narrative that belongs elsewhere.
Requirements
The rule is fixed first (branch doc/no-version-history-in-docstrings): exactly one .. versionadded:: per public symbol; never a .. versionchanged:: stack at class or method level; a behaviour change is annotated on the parameter that changed, otherwise it lives in the changelog. Mirrored into .claude/rules/docstrings.md so it reaches an agent before it writes.
Maintainer review of the 40 existing .. versionchanged:: entries — for each: move onto its parameter, or delete in favour of the changelog. This is a docstring-only sweep, no behaviour change, byte-identical output.
Maintainer review of the 29 docstrings >= 100 lines, worst-first. For each block decide: reference material (keep, tightened), user-guide material (move to docs/source/index/usage_principles/), protocol material (move to the relevant protocol notebook), or changelog material (delete).
Decide the target ceiling. A stated maximum (e.g. a method docstring under ~80 lines, a constructor under ~60) turns this from taste into something a checker can enforce.
Optional, only if a ceiling is agreed: extend .claude/skills/docstrings/scripts/check_docstrings.py with a length advisory, and consider a backlog ratchet in the style of the pyright and docs-build gates, so the count cannot grow again.
Verify the TMD glossary correction (branch doc/tmd-is-target-middle-domain) reads the way the maintainer wants, since it is the canonical statement six other places now point at.
KPIs / Acceptance criteria
Zero .. versionchanged:: directives at class or method level; the ones that survive are attached to a parameter.
CPP.__init__ back under 60 lines and CPP.run under 100, with nothing deleted that a user needs, only relocated.
Every relocated block has a new home that is linked from the docstring it left.
Docs build stays at zero docutils errors and the CRITICAL count does not rise.
df_feat is byte-identical before and after the sweep (it is documentation only).
Scope / non-goals
Documentation only. No signature, default, behaviour or output change. CPP's algorithm is not touched.
Not a rewrite of every docstring: the >= 100-line set first, and only where the maintainer agrees the content belongs elsewhere.
Not a new doc system, and no ADR references in docstrings.
Problem
The docstrings have grown into changelogs and design notes. This was not one careless change: the style guide asked for it, and every agent followed it literally.
Measured on
origin/master(3c11c909):_cpp.pytotal linesCPP.__init__docstringPackage-wide (public modules,
_backendexcluded): 95 docstrings are >= 60 lines, 29 are >= 100. The longest:CPPPlot.feature_mapCPP.runCPPPlot.heatmapEmbeddingPreprocessor.fetch_embeddingsCPP.run_numAAPredPlot.predict_groupTwo distinct causes, both traceable:
1. Version history is rendered into the API pages.
CPP.runopens with "Added in version 0.1.0" followed by three stacked "Changed in version 1.1.0" blocks, so a reader meets the method's history before its behaviour. Package-wide: 242versionadded+ 40versionchanged, 11 of the latter in_cpp.pyalone. Origin: commita4537eed(2026-06-01), a deliberate backfill pass, followingdocs/source/index/docstring_guide.rst, which said ".. versionchanged:: X.Y.Zwhen behaviour changes" and offered the parameter-level form only as an alternative.2. Each feature wrote its rationale into the constructor. Every block in
CPP.__init__traces to the commit that added the feature:bootstrap(b6e53f9f),bootstrap_kwskey-by-key (4d514213),ciplus the "Uncertainty per feature" note (41d87157),n_batches(6407b52a),n_sample_batches(c8cb7f73), the splits auto-cap note (4030c9f9), the "intrinsically binary" multi-class note (bd64516b), the_kwspreamble (5451c3eb). Each is defensible alone; together they are a design document in a constructor docstring.A related drift, same root cause, already corrected in a branch:
CONTEXT.mdanddocs/source/index/glossary.rstdescribed the part vocabulary as "TMD-centric" and "semantically wrong", prescribing a rename, while the published Feature Identification chapter has always said Target Middle Domain. Three independent planning passes each designed a CPP region-naming layer from that text alone.Goal
Docstrings say what an object does today, in the house voice; history lives in
CHANGELOG.mdand the release notes; rationale lives in the user guide and protocols. Bring the worst offenders back to that, with the maintainer deciding what is genuinely reference material and what is narrative that belongs elsewhere.Requirements
doc/no-version-history-in-docstrings): exactly one.. versionadded::per public symbol; never a.. versionchanged::stack at class or method level; a behaviour change is annotated on the parameter that changed, otherwise it lives in the changelog. Mirrored into.claude/rules/docstrings.mdso it reaches an agent before it writes... versionchanged::entries — for each: move onto its parameter, or delete in favour of the changelog. This is a docstring-only sweep, no behaviour change, byte-identical output.docs/source/index/usage_principles/), protocol material (move to the relevant protocol notebook), or changelog material (delete)..claude/skills/docstrings/scripts/check_docstrings.pywith a length advisory, and consider a backlog ratchet in the style of the pyright and docs-build gates, so the count cannot grow again.doc/tmd-is-target-middle-domain) reads the way the maintainer wants, since it is the canonical statement six other places now point at.KPIs / Acceptance criteria
.. versionchanged::directives at class or method level; the ones that survive are attached to a parameter.CPP.__init__back under 60 lines andCPP.rununder 100, with nothing deleted that a user needs, only relocated.df_featis byte-identical before and after the sweep (it is documentation only).Scope / non-goals
Standards checklist
Returns, per-methodExamplesinclude)python .claude/skills/docstrings/scripts/check_docstrings.py <path>reports 0 defectsdoc_signature_drift.pyreports 0 driftpython .github/scripts/check_docs_build.pypasses: 0 errors, CRITICAL not above baseline