| name | doc-hunter-py |
| description | Audit Python code for missing or misleading inline documentation where the "why" is not obvious —
obscure calculations, non-trivial business rules, surprising behavior, implicit constraints,
workarounds, and stale comments that contradict the current code. Finds where a comment would
save the next reader significant time, or where an existing comment actively misleads.
Use when: reviewing Python code for long-term maintainability, onboarding new team members,
auditing undocumented business logic, or preparing code for handoff.
Reports omit empty sections — no placeholder headings, empty tables, or negative statements like "no issues found".
|
| disable-model-invocation | true |
Doc Hunter
Audit code for missing or misleading "why" documentation — places where the intent, constraints, or reasoning
behind the code are not obvious from the code itself, or where existing comments contradict the current behavior. The
goal: every non-obvious decision has a concise inline explanation, no comment restates what the code already says,
and no comment actively lies about what the code does.
When to Use
- Reviewing code for long-term maintainability
- Onboarding new team members onto an undocumented codebase
- Auditing business logic that lives only in developers' heads
- Preparing code for handoff to another team
- After a complex feature lands, before the author's context fades
Core Principles
-
Document the why, never the what. A comment should answer "why is this here?" or "why this approach?" — never
"what does this line do?". If the code needs a "what" comment, the code itself should be rewritten for clarity
(simplicity-hunter territory, not doc-hunter).
-
Absence and staleness are both signals. Doc-hunter looks for places where documentation is missing and
places where it is actively misleading. A complex calculation with no comment is a finding. A comment that
describes behavior the code no longer performs is a finding. Redundant but accurate comments are slop-hunter
territory.
-
Concise over comprehensive. The best inline comment is one sentence that captures the key insight. If a
paragraph is needed, the logic may need extraction into a named function instead.
-
Context decays. The reason behind a workaround, a magic number, or a non-obvious branch is obvious to the author
today and a mystery to everyone (including the author) in six months. The audit identifies where that decay will
hurt most.
-
Not every line needs a comment. Straightforward code — standard patterns, clear naming, obvious control flow —
should stand on its own. Only flag missing documentation where a competent reader of the language and domain would
genuinely pause and ask "why?".
What to Hunt
1. Unexplained Business Rules
Conditionals, thresholds, or branching logic that encode domain rules not evident from the code alone.
Signals:
if age >= 26 — why 26? Legal requirement? Business policy?
discount = 0.1 if total > 500 else 0 — what drives the 500 threshold?
- Complex eligibility checks with multiple conditions and no explanation of the rule
- Branching on enum/literal values where the reason one variant is handled differently is non-obvious
Action: Flag for a comment explaining the business rule, its source (regulation, product spec, stakeholder
decision), and when it might change.
2. Magic Numbers and Constants
Literal values embedded in logic whose meaning or origin is unclear.
Signals:
- Numeric literals in calculations:
timeout = retries * 1.5 + 3
- String literals used as keys or identifiers without explanation
- List/tuple indices that encode positional meaning:
parts[2]
- Bit masks, status codes, or protocol values used without context
Action: Flag for either a named constant with a descriptive name, or an inline comment explaining the value's
origin and meaning.
3. Non-Obvious Algorithms and Calculations
Mathematical formulas, coordinate transforms, bit manipulation, or multi-step data transformations where the approach
is not self-evident.
Signals:
- Arithmetic involving domain-specific formulas (finance, geometry, physics, statistics)
- Bitwise operations (
<<, >>, &, |, ^) outside obvious flag-checking contexts
- Multi-step list/dict comprehensions where the intermediate goal is unclear
- Sorting key functions with non-trivial logic
- Regular expressions beyond simple patterns
Action: Flag for a comment explaining what the calculation achieves, what the inputs/outputs represent, and (for
formulas) a reference to the source (spec, paper, algorithm name).
4. Workarounds and Compensations
Code that exists to work around a bug, library limitation, platform quirk, or upstream constraint.
Signals:
- Code that looks unnecessarily complex for what it achieves
- Patterns that contradict the project's normal style in a localized way
- Defensive code that handles a case the types say shouldn't happen
- Timeouts, retries, or delays without explanation of what they're compensating for
- Conditional logic that checks for specific platform/version/environment
# type: ignore or cast() without explanation of why the type system can't express the constraint
Action: Flag for a comment explaining what is being worked around, ideally with a link to the issue/bug tracker,
and under what conditions the workaround can be removed.
5. Implicit Ordering and Timing Dependencies
Code where the execution order matters but isn't enforced by the type system or control flow.
Signals:
- Functions that must be called in a specific sequence (init before use, A before B)
- State mutations that depend on prior mutations having occurred
- Signal/event handler registration that assumes a particular lifecycle order
- Async operations that depend on a prior operation having completed (without explicit await)
- Operations that assume sorted input without asserting it
Action: Flag for a comment explaining the ordering constraint and what breaks if violated.
6. Surprising Behavior and Edge Cases
Code whose behavior differs from what a reasonable reader would expect.
Signals:
- A function that mutates its input when the name suggests a pure operation
- Return values with non-obvious semantics (
None means "not found" vs "not applicable" vs "error")
- Side effects not indicated by the function name or signature
- Intentionally bare
except clauses or except Exception blocks (why is ignoring the error correct here?)
- Boundary conditions handled differently (first/last element, empty input, zero, negative values)
- Mutable default arguments used intentionally (e.g., sentinel pattern)
Action: Flag for a comment explaining the surprising behavior and why it is intentional.
7. Non-Obvious Public API Contracts
Exported functions or methods whose usage constraints are not captured by the type signature.
Signals:
- Parameters with valid ranges not expressed in the type (
percent: float — is 0-1 or 0-100?)
- Functions that raise on certain inputs without the signature indicating it
- Methods with preconditions (must call X first, object must be in state Y)
- Return values with ownership semantics (caller must close, must not mutate)
- Thread/async safety constraints not evident from the signature
- Generator functions whose protocol (send/throw behavior) is unclear
Action: Flag for docstring or inline comment documenting the contract. For public API, docstring is preferred; for
internal functions, a brief inline comment suffices.
8. Stale or Misleading Documentation
Comments, docstrings, or inline explanations that describe behavior the code no longer exhibits.
Signals:
- Comment describes an algorithm or flow that has since been rewritten
- Docstring parameter list doesn't match the current function signature (missing params, renamed params, wrong types)
- Comment says "returns X" but the function now returns Y
- TODO/FIXME referencing a condition that has already been resolved
- Comment explaining a workaround for a bug that has since been fixed (workaround removed but comment remains)
- Inline comment describing a branch condition that no longer exists
Action: Update or remove the stale documentation. If the comment once explained a valid "why" that is still
relevant, update it to match current behavior. If the rationale no longer applies, delete it.
Audit Workflow
Phase 1: Gain Context
- Resolve audit surface. The prompt may specify the scope as:
- Diff: files changed on the current branch vs base (
main/master)
- Path: specific files, folders, or layers
- Codebase: the entire project
If unspecified, default to codebase. For diff mode, resolve the file list:
BASE=$(git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null | sed 's@^refs/remotes/origin/@@' || echo main)
SCOPE=$(git diff --name-only $(git merge-base HEAD $BASE)...HEAD)
Constrain all subsequent scans to the resolved surface.
- Understand the project's domain — what business rules, algorithms, or protocols does it implement?
- Note existing documentation patterns (docstring style — Google, NumPy, Sphinx; inline comment conventions; README
structure).
Phase 2: Scan for Documentation Gaps
EXCLUDE='--glob !**/*_test.py --glob !**/test_*.py --glob !**/tests/** --glob !**/venv/** --glob !**/.venv/** --glob !**/dist/**'
rg --pcre2 '[^0-9][2-9]\d{1,}[^0-9]|0x[0-9a-f]{2,}|\d+\.\d+' --type py $EXCLUDE
rg --pcre2 "re\.(compile|search|match|findall|sub)\s*\(" --type py $EXCLUDE
rg --pcre2 '<<|>>|[^&]&[^&]|[^|]\|[^|]|\^' --type py $EXCLUDE
rg 'time\.sleep|asyncio\.sleep|timeout' --type py $EXCLUDE
rg -i 'hack|workaround|fixme|todo' --type py $EXCLUDE
rg '# type: ignore(?!\[)' --type py $EXCLUDE
These are heuristic starting points. The primary method is reading the code and identifying where a competent reader
would ask "why?" — no grep pattern can substitute for that judgment.
Phase 3: Evaluate Each Gap
For each candidate, determine:
- Would a competent reader of this language and domain genuinely pause here?
- Is the "why" recoverable from context (nearby code, naming, types), or is it truly missing?
- Is a comment the right fix, or should the code be restructured for clarity instead?
Classify each as:
- Add comment: the why is missing and a comment is the right fix
- Rename/extract: the code would be self-documenting with better naming or extraction
- Skip: the code is clear enough to a domain-literate reader
Phase 4: Produce Report
Output Format
Save as YYYY-MM-DD-doc-hunter-audit-{$LLM-name}.md in the project's docs folder (or project root if no docs folder
exists).
# Doc Hunter Audit — {date}
## Scope
- Surface: {diff / path / codebase}
- Files: {count or list}
- Exclusions: {list}
## Findings
### Unexplained Business Rules
| # | Location | Code | Missing Context | Suggested Comment |
| - | -------- | ---- | --------------- | ----------------- |
| 1 | file:line | `if age >= 26` | Why 26? | `# Minimum age for policy X per regulation Y` |
### Magic Numbers
| # | Location | Value | Context | Action |
| - | -------- | ----- | ------- | ------ |
| 1 | file:line | `1.5` in retry calc | Backoff multiplier — origin unclear | Name as constant or comment source |
### Non-Obvious Algorithms
| # | Location | Code | Missing Context | Action |
| - | -------- | ---- | --------------- | ------ |
| 1 | file:line | Haversine formula | No reference to formula name or source | Add `# Haversine distance — see <ref>` |
### Workarounds
| # | Location | Code | Missing Context | Action |
| - | -------- | ---- | --------------- | ------ |
| 1 | file:line | 200ms sleep before retry | Why the delay? What's it compensating for? | Comment with root cause and removal condition |
### Ordering Dependencies
| # | Location | Code | Missing Context | Action |
| - | -------- | ---- | --------------- | ------ |
| 1 | file:line | `init()` must precede `start()` | No indication of required ordering | Add comment or assert precondition |
### Surprising Behavior
| # | Location | Code | What's Surprising | Action |
| - | -------- | ---- | ----------------- | ------ |
| 1 | file:line | `process()` mutates input list | Name implies pure function | Comment or rename to `process_in_place()` |
### API Contracts
| # | Location | Signature | Missing Contract | Action |
| - | -------- | --------- | ---------------- | ------ |
| 1 | file:line | `set_opacity(value: float)` | Valid range? 0-1 or 0-100? | Add docstring with param range |
## Recommendations (Priority Order)
1. **Must-fix**: {undocumented business rules, workarounds without context, magic numbers in core logic}
2. **Should-fix**: {non-obvious algorithms, surprising behavior, API contracts}
3. **Consider**: {ordering dependencies, minor magic numbers in non-critical paths}
Operating Constraints
- No code edits. This skill produces an audit report only. Implementation is a separate step.
- No empty sections. Include only categories with findings. Omit a heading, table, or list entirely when it would contain zero items — do not include empty tables, placeholder subsections, or negative statements like "no dead exports", "none found", or "no issues".
- Scope: missing or misleading "why" documentation only. Do not flag redundant or verbose comments (→ slop-hunter-py), structural
complexity (→ simplicity-hunter-py), type invariants (→ invariant-hunter-py), type design (→ type-hunter-py), module
boundary issues (→ boundary-hunter-py), class/interface design (→ solid-hunter-py), security (→ security-hunter-py),
or test quality (→ test-hunter-py). If a finding doesn't answer "would a reader pause here and ask why?", it doesn't
belong here.
- Evidence required. Every finding must cite
file/path.py:line with the exact code.
- Judgment over pattern-matching. Grep can find magic numbers and regex literals, but only reading the code reveals
whether the "why" is truly missing. Prioritize manual review of complex logic over mechanical scanning.
- Suggest, don't prescribe. The "Suggested Comment" column is a starting point. The author knows the actual "why" —
the audit identifies where it's missing, not what it should say.
- Respect domain expertise. Code that looks obscure to a generalist may be obvious to a domain expert. When
uncertain, flag as "Consider" rather than "Must-fix".