Skip to content

Docstring style drift: version history and feature rationale have moved into the docstrings #581

Description

@breimanntools

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):

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.

Standards checklist

  • numpydoc shape preserved (named Returns, per-method Examples include)
  • python .claude/skills/docstrings/scripts/check_docstrings.py <path> reports 0 defects
  • doc_signature_drift.py reports 0 drift
  • python .github/scripts/check_docs_build.py passes: 0 errors, CRITICAL not above baseline

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    prio:2Importanttopic:coreCore featurestype:docsImprovements or additions to documentation

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions