# Docstring Check

> Scans a codebase for missing, outdated, drifted, or inconsistent docstrings and applies behavior-preserving fixes matching the project's detected convention.

- **Type:** Skill
- **Install:** `agentstack add skill-thijsvos-claude-skills-docstring-check`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [thijsvos](https://agentstack.voostack.com/s/thijsvos)
- **Installs:** 0
- **Category:** [Agent Skills](https://agentstack.voostack.com/c/agent-skills)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [thijsvos](https://github.com/thijsvos)
- **Source:** https://github.com/thijsvos/Claude_Skills/tree/main/skills/docstring-check
- **Website:** https://github.com/thijsvos/Claude_Skills#readme

## Install

```sh
agentstack add skill-thijsvos-claude-skills-docstring-check
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

Call `EnterPlanMode` immediately before doing anything else.

You are performing a comprehensive docstring audit and — after user approval — applying convention-matching fixes. Scan the target code for three classes of problem in parallel: missing docstrings on public API, signature-vs-docstring drift, and style/convention inconsistency. Synthesize a prioritized fix plan, apply changes incrementally, and verify with the project's existing linter or doc-build tool.

**ARGUMENTS:** The user may provide an optional target argument — a file path, directory, function/class name, branch name, commit range, or natural language description of what to audit. If no argument is provided, default to a full-codebase scan (asking the user to narrow the scope if the repo is large).

**IMPORTANT:** Always quote the user-supplied argument in double quotes when passing it to shell commands.

---

## Step 1: Resolve Scope and Detect Project Context

Determine what code to audit based on the argument and project state.

**If an argument was provided**, resolve it in this order:

1. **File path** — if the path exists on disk as a file, audit that file:
   ```bash
   test -f "" && echo "file"
   ```
   Read the file in full and identify all documentable symbols (functions, methods, classes, modules, exported constants).

2. **Directory path** — if the path is a directory, find all source files in it:
   ```bash
   test -d "" && echo "directory"
   ```
   Find source files (exclude test files, node_modules, vendor, build artifacts, generated code):
   ```bash
   find "" -type f \( -name '*.ts' -o -name '*.tsx' -o -name '*.js' -o -name '*.jsx' -o -name '*.py' -o -name '*.go' -o -name '*.rb' -o -name '*.rs' -o -name '*.java' -o -name '*.kt' -o -name '*.swift' -o -name '*.php' -o -name '*.cs' \) ! -path '*/node_modules/*' ! -path '*/vendor/*' ! -path '*/__pycache__/*' ! -path '*/dist/*' ! -path '*/build/*' ! -path '*/target/*' ! -path '*/.venv/*' ! -path '*/venv/*' ! -name '*.test.*' ! -name '*.spec.*' ! -name '*_test.*' ! -name 'test_*.py' | head -50
   ```
   If the directory contains more than 30 source files, list a summary by language and ask the user to narrow the scope or confirm they want to proceed (up to 50 files maximum).

3. **Function, class, or method name** — if the argument is not a valid path, search the codebase for it:
   ```bash
   grep -rn --include='*.ts' --include='*.tsx' --include='*.js' --include='*.jsx' --include='*.py' --include='*.go' --include='*.rb' --include='*.rs' --include='*.java' --include='*.kt' --include='*.swift' --include='*.php' --include='*.cs' -E "(function|def|func|class|fn|pub fn|export|interface|struct|enum|trait|impl)\s+" . 2>/dev/null | grep -v node_modules | grep -v vendor | head -10
   ```
   If found in multiple files, list them and ask the user to confirm which one. Read the full file(s) containing the match.

4. **Git ref** (branch or tag) — if `git rev-parse --verify ` succeeds and it is not a file path, identify files changed on that ref compared to the default branch:
   ```bash
   default_branch=$(git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null | sed 's@^refs/remotes/origin/@@')
   [ -z "$default_branch" ] && git rev-parse --verify main >/dev/null 2>&1 && default_branch=main
   [ -z "$default_branch" ] && git rev-parse --verify master >/dev/null 2>&1 && default_branch=master
   ```
   ```bash
   git diff "$default_branch"..."" --name-only --diff-filter=ACMR 2>/dev/null
   ```
   Read those files in full for docstring analysis.

5. **Commit range** — if the argument contains `..`, use it directly:
   ```bash
   git diff "" --name-only --diff-filter=ACMR 2>/dev/null
   ```
   Read those files in full.

6. **Natural language description** — if none of the above match, interpret the argument as a description of a code area (e.g., "the authentication module", "API handlers"). Extract keywords, search the codebase, present found files, and ask the user to confirm scope.

7. If none of the above produce results, inform the user and stop:
   > Could not resolve the argument as a file path, directory, code identifier, git ref, or code area description. Try: `/docstring-check src/auth/handler.ts` (file), `/docstring-check src/utils/` (directory), `/docstring-check handleLogin` (function), `/docstring-check feature-branch` (branch), or `/docstring-check` (full-codebase scan).

**If no argument was provided**, default to a **full-codebase scan**. Enumerate source files:

```bash
find . -type f \( -name '*.ts' -o -name '*.tsx' -o -name '*.js' -o -name '*.jsx' -o -name '*.py' -o -name '*.go' -o -name '*.rb' -o -name '*.rs' -o -name '*.java' -o -name '*.kt' -o -name '*.swift' -o -name '*.php' -o -name '*.cs' \) ! -path './node_modules/*' ! -path './vendor/*' ! -path './__pycache__/*' ! -path './dist/*' ! -path './build/*' ! -path './target/*' ! -path './.venv/*' ! -path './venv/*' ! -path './.git/*' ! -name '*.test.*' ! -name '*.spec.*' ! -name '*_test.*' ! -name 'test_*.py'
```

Also exclude generated files (those starting with a `DO NOT EDIT` banner):
```bash
grep -l -m1 "DO NOT EDIT"  2>/dev/null
```

If the scan returns **more than 50 files**, summarize the breakdown by language (e.g., "127 Python files, 34 TypeScript files, 8 Go files") and use `AskUserQuestion` to ask the user how to narrow the scope. Offer these options:

- **Full scan** — audit all source files (higher cost, complete coverage)
- **Hotspots** — scope to the top 30 most-edited files in the last 12 months:
  ```bash
  git log --format=format: --name-only --since=12.months 2>/dev/null | grep -E '\.(ts|tsx|js|jsx|py|go|rb|rs|java|kt|swift|php|cs)$' | sort | uniq -c | sort -rn | head -30 | awk '{print $2}'
  ```
- **Changed files only** — scope to files changed on the current branch vs the default branch (same `default_branch` detection as above)
- **Public API only** — scope to only the top-level/exported files (language-dependent: `__init__.py`, `index.ts`, `mod.rs`, `lib.rs`, files without a leading underscore)

**After resolving the scope**, gather project context by reading these files if they exist:

- `CLAUDE.md` — project conventions
- `pyproject.toml`, `package.json`, `Cargo.toml`, `go.mod`, `Gemfile`, `pom.xml`, `build.gradle`, `composer.json` — language detection + dependency list
- **Docstring style configuration** (authoritative — if present, use instead of inferring):
  - Python: `[tool.pydocstyle]` or `[tool.ruff.lint.pydocstyle]` in `pyproject.toml`, `.pydocstyle`, `setup.cfg` `[pydocstyle]` section
  - TS/JS: `tsdoc.json` (TSDoc), `.eslintrc*` / `eslint.config.*` with `plugin:jsdoc`
  - Ruby: `.rubocop.yml` `Style/Documentation` / `Style/DocumentationMethod`
  - Java: Checkstyle XML `MissingJavadoc*` rules
- **Doc-build infrastructure** (used for verification in Step 4):
  - `docs/conf.py` (Sphinx), `mkdocs.yml`, `typedoc.json`, `Doxyfile`, `Cargo.toml` → `cargo doc`, `godoc`

**Detect the in-use docstring style** if not explicitly configured. Sample 15–20 existing non-trivial docstrings from the scoped files and classify:

- **Python**: `Args:`/`Returns:` → Google; `Parameters\n----------\n` → NumPy; `:param x:`/`:returns:` → reST/Sphinx; otherwise PEP 257 plain
- **TS/JS**: presence of `@param {Type}` in a TS file → JSDoc-in-TS (discouraged); absence of type tags on TS → TSDoc; typed `@param {Type}` on JS → JSDoc
- **Go**: fixed — godoc expects `// FuncName does …` on exported identifiers
- **Rust**: fixed — `///` for item-level, `//!` for module-level, Markdown body
- **Java**: fixed — `/** … */` with `@param`, `@return`, `@throws`
- **C#**: fixed — `/// `, ``, ``, ``
- **Ruby**: `# @param [Type]` → YARD; freeform with `+code+`/`*bold*` → RDoc
- **PHP**: fixed — PHPDoc `/** @param Type $x */`

**Detect available docstring linters** (so Step 2 can delegate mechanical checks):

- Python: `ruff --select D --no-fix`, `pydocstyle`, `interrogate`, `pydoclint`, `darglint`
- TS/JS: `eslint-plugin-jsdoc` (check if in `package.json` deps)
- Go: `go vet`, `staticcheck` (ST1020/ST1021/ST1022), `revive`
- Rust: `#![warn(missing_docs)]` in crate roots; `cargo doc --no-deps 2>&1 | grep warning`
- Java: `javadoc -Xwerror`, Checkstyle
- C#: Roslyn `CS1591` via `dotnet build -warnaserror:CS1591`

For each candidate linter, verify it's actually runnable (`command -v ` or present in project deps). Record the runnable set.

State the resolved scope, file count, detected language(s), detected docstring style, available linters, and detected doc-build tool clearly before proceeding.

---

## Step 2: Multi-Dimensional Docstring Analysis

Launch **3 Explore subagents in parallel** (`subagent_type: "Explore"`, `model: "opus"`).

Provide each agent with:
- The resolved scope (file list) from Step 1
- The detected docstring style (or explicit project configuration)
- The list of runnable linters
- The language(s) present

**IMPORTANT:** All subagents MUST be launched with `subagent_type: "Explore"` and `model: "opus"` (resolves to the latest Claude Opus, the most capable model). The Explore agent is read-only by design (Edit and Write are denied at the agent level). This ensures no subagent can accidentally modify the project during analysis. The model override to Opus is required because Explore defaults to Haiku, which lacks the depth needed for this skill's thorough analysis. Never use general-purpose subagents in this skill.

**IMPORTANT:** Instruct each agent to read the **full target files** (not just snippets). Understanding the function body is essential for both drift detection (does the docstring describe what the code actually does?) and proposed-content generation (what should the docstring say?).

Each agent must return findings in this structured format:
- **ID**: agent-local identifier (e.g., A1, B1, C1)
- **File:Line**: exact file path and line number of the symbol
- **Symbol**: the function/class/method/constant name and signature
- **Current docstring**: the existing docstring verbatim, or `"(missing)"`
- **Proposed docstring**: the complete replacement text in the detected project style
- **Rationale**: why this change improves the docstring
- **Confidence**: High / Medium / Low (how certain the agent is this is a real issue)
- **Severity**: Critical / High / Medium / Low

Each agent must also return 2-3 **"Looks Good"** callouts — symbols that are already well-documented and should NOT be changed. This prevents unnecessary rewrites and acknowledges good practice.

---

### Agent 1: Coverage & Presence

Identify symbols that lack docstrings entirely, with emphasis on the public API surface.

**Public API detection (language-specific):**

- **Python**: non-underscore identifiers at module top level (respect `__all__` if defined — symbols in `__all__` are always public; symbols not in `__all__` are internal even if non-underscore)
- **TS/JS**: `export` keyword (named or default); top-level declarations in files re-exported via `index.ts`
- **Go**: identifiers starting with a capital letter at package level
- **Rust**: items with `pub` / `pub(crate)` / `pub(super)` visibility
- **Java/C#**: `public` (also `protected` if the class is extensible)
- **Ruby**: methods outside `private` / `protected` blocks
- **PHP**: methods with `public` visibility; classes without explicit visibility

**Also check:**

- Any runnable linter detected in Step 1 — run it and capture structured output. Parse findings into the agent's format.
- Module-level / file-level / crate-level documentation (Python module docstring, Go package comment, Rust `//!`, TSDoc `@packageDocumentation`).

**Severity assignment:**

- **Critical** — public API symbol with zero docstring
- **High** — public API symbol with a single-line docstring shorter than 10 words
- **Medium** — internal symbol with a complex signature (3+ parameters, or raises/returns non-trivially) and no docstring
- **Low** — internal symbol with a simple signature and no docstring

Skip: test files, generated code, `__init__.py` files that only re-export, trivial getters/setters in some languages if the convention is to skip them. Respect `.gitignore`.

Return findings sorted by severity (Critical first), and 2-3 Looks Good callouts (e.g., "The public API of `src/auth/` is consistently documented at the module level").

---

### Agent 2: Accuracy & Drift

For every function/method that *has* a docstring in the scoped files, verify it still matches the code. This is the agent that catches bugs.

**Drift categories:**

- **Param drift** — documented parameters don't match the actual signature:
  - Renamed (docstring says `user_id`, signature has `uid`)
  - Reordered (docstring lists params in different order than signature)
  - Added (signature has a param not documented)
  - Removed (docstring describes a param that no longer exists)
- **Missing return documentation** — function returns a non-void/non-None value but has no `@returns` / `Returns:` / `:returns:` / `` section
- **Missing error documentation** — function throws/raises but has no `@throws` / `Raises:` / `:raises:` / `` — check for `throw`, `raise`, `panic!`, `return Err(...)` in the body
- **Type mismatch** — the docstring-declared type contradicts the actual type annotation (e.g., JSDoc `@param {string}` on a TS function whose signature types it as `number`; Python docstring says `int` but annotation says `str`)
- **Copy-paste rot** — identical docstring on symbols with different signatures. Detect by hashing docstring text and flagging duplicates across different signatures.
- **Stale description** — the docstring describes behavior the code no longer exhibits (e.g., mentions a side effect that has been removed, or references a removed dependency). Only flag at **High confidence**; this is the hardest category and false positives are costly.
- **Example drift** — code examples in docstrings reference APIs that no longer exist (e.g., example imports a removed symbol).

**Severity assignment:**

- **Critical** — param drift on public API (callers will be misled), or type mismatch on public API
- **High** — missing `@returns`/`@throws` on public API, stale description on public API
- **Medium** — drift on internal API, copy-paste rot
- **Low** — example drift, minor wording issues

For each finding, provide the corrected docstring that reflects the current code. Do not speculate about the original author's intent — describe what the code actually does now.

Return findings and 2-3 Looks Good callouts (e.g., "The database layer's docstrings consistently and accurately document thrown exceptions").

---

### Agent 3: Style & Convention Consistency

Flag docstrings that deviate from the project's detected style or lack the informational quality a reader needs.

**Checks:**

- **Style deviation** — files using a different style from the project default (NumPy-style in a Google-style Python project; JSDoc type tags in a TSDoc project; single-line godoc that doesn't start with the identifier name in Go)
- **Intra-docstring inconsistency** — mixed tag conventions within a single docstring (e.g., `Args:` followed by `:returns:` in Python)
- **Under-informative docstrings** — docstrings that merely restate the function name ("does_login: does the login", "get_user: gets a user") without adding information the signature doesn't already convey
- **Formatting violations** (language-specific):
  - Python PEP 257: first line not ending in a period, first line not a complete sentence, no blank line between summary and body for multi-line docstrings, triple single quotes instead of triple double quotes
  - Go: doc comment doesn't start with the identifier name
  - Rust: missing `# Examples`, `# Panics`, `# Errors`, `# Safety` sections where appropriate (e.g., `unsafe fn` should document safety invariants)
  - Javadoc: missing `@param` for documented parameters, `{@link}` pointing to non-existent types
  - TSDoc: use of `@param {type}` (banned — types belong in TS signature)
- **Link rot** — `{@link Foo}`, `[Foo]`, `{@see

…

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [thijsvos](https://github.com/thijsvos)
- **Source:** [thijsvos/Claude_Skills](https://github.com/thijsvos/Claude_Skills)
- **License:** MIT
- **Homepage:** https://github.com/thijsvos/Claude_Skills#readme

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** no
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** no
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-thijsvos-claude-skills-docstring-check
- Seller: https://agentstack.voostack.com/s/thijsvos
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
