SkillsHooksPromptsAgentsPersonasModelsPoliciesToolsTemplatesBundlesCategoriesStart here
← Hooks
Hk hookgovernanceadvisorystable

No Commented-Out Code

Fires on PostToolUse for Edit and Write tools. Scans the written file content for commented-out code blocks — lines that appear to be executable code hidden behind a comment marker rather than explanatory prose. Emits a DEAD-CODE-VIOLATION warning when detected. Non-blocking.

id hook/no-commented-codev1.0.0by convergent-systems-key
Event
PostToolUse
Trigger
tool-nameEdit|Write
Language
python
Side effects
  • emits DEAD-CODE-VIOLATION sentinel lines to stderr
  • reports file path and approximate line numbers of violations
Platforms
linuxmacoswindows
Notes
Pure Python, no git dependency. Reads file path from the PostToolUse payload. Cross-platform via 'ai hooks run'.
Depends on
hook/lib

Script · no-commented-code.py

#!/usr/bin/env python3
"""hooks/no-commented-code.py — detect commented-out executable code in written files.

Fires on PostToolUse for Edit and Write. Reads the just-written file and
scans for commented-out code: lines that look like executable statements
hidden behind a comment marker rather than explanatory prose.

Language detection is extension-based. Supported languages:
  # comments: Python, Ruby, Shell, YAML, TOML, R
  // comments: JS, TS, Go, Rust, Java, Kotlin, Swift, C, C++, C#, PHP
  -- comments: SQL, Lua, Haskell
  ; comments: Lisp, Clojure
  <!-- -->: HTML, XML, SVG

Heuristics for 'this looks like code, not a comment':
  - Contains a function/method call: word followed by '(' or '::'
  - Contains an assignment: '=', ':='
  - Contains a control-flow keyword at the line start after the marker:
    'if', 'else', 'for', 'while', 'return', 'def', 'function', 'class',
    'import', 'from', 'require', 'use', 'let', 'var', 'const'
  - Ends with '{', '}', ';' (structural code tokens)
  - Starts with a decorator: '@' followed by word characters

Excludes:
  - Doc-comment blocks (lines starting with ///, //!, /*!, ////, #!)
  - Lines that are clearly prose: no code tokens, purely natural language
  - Shebangs (#!)
  - Single-word commented lines ('# TODO', '# type: ignore', etc.)

Non-blocking: emits DEAD-CODE-VIOLATION to stderr.

Self-check:
  --self-check  exits 0 always (no external deps).
"""
from __future__ import annotations

import json
import re
import sys
from pathlib import Path

sys.path.insert(0, str(Path(__file__).resolve().parent))
import _lib  # noqa: E402


# ---------------------------------------------------------------------------
# Language → comment marker mapping
# ---------------------------------------------------------------------------

_HASH_EXTS = {
    ".py", ".rb", ".sh", ".bash", ".zsh", ".fish",
    ".yml", ".yaml", ".toml", ".r", ".R",
}
_SLASH_EXTS = {
    ".js", ".jsx", ".ts", ".tsx", ".mjs", ".cjs",
    ".go", ".rs", ".java", ".kt", ".kts",
    ".swift", ".c", ".h", ".cpp", ".cc", ".cxx", ".hpp",
    ".cs", ".php",
}
_DASH_EXTS = {".sql", ".lua", ".hs", ".lhs"}
_SEMI_EXTS = {".lisp", ".clj", ".cljs", ".el", ".scm"}
_HTML_EXTS = {".html", ".htm", ".xml", ".svg"}


def _comment_prefix(path: Path) -> str | None:
    ext = path.suffix.lower()
    if ext in _HASH_EXTS:
        return "#"
    if ext in _SLASH_EXTS:
        return "//"
    if ext in _DASH_EXTS:
        return "--"
    if ext in _SEMI_EXTS:
        return ";"
    return None  # HTML handled separately; unsupported → skip


# ---------------------------------------------------------------------------
# Detection regexes
# ---------------------------------------------------------------------------

# A call: word chars followed by '(' or '::' (bare ':' excluded — matches English prose like 'TODO:')
_CALL_RE = re.compile(r"\w+\s*(?:\(|::)")
# An assignment
_ASSIGN_RE = re.compile(r"(?<!=)=(?!=)|:=")
# Control-flow keywords at the start of the content after the marker
_KEYWORD_RE = re.compile(
    r"^(?:if|else(?:if)?|elif|for|while|do|switch|match|return|yield|"
    r"def|function|class|struct|enum|interface|impl|trait|"
    r"import|from|require|use|include|"
    r"let|var|const|val|mut|pub|private|protected|static|"
    r"try|catch|finally|raise|throw|async|await)\b"
)
# Structural code tokens
_STRUCTURAL_RE = re.compile(r"[{}];?\s*$|;\s*$")
# Decorator
_DECORATOR_RE = re.compile(r"^@\w+")
# Doc-comment prefixes to skip
_DOC_RE = re.compile(r"^(?:///!?|//!|/\*!|#{4,}|#!)")
# Single identifier line (e.g. '# TODO', '# type: ignore', '# noqa: E501')
# Uses \w+ (not \S+) so that structural tokens like '}' and calls like 'fn(x)' are not excluded early.
_SINGLE_TOKEN_RE = re.compile(r"^\w+(?:\s*:\s*\w+(?:\.\w+)*)?\s*$")


def _looks_like_code(content: str) -> bool:
    """Return True if the stripped content after the comment marker looks
    like executable code rather than prose."""
    s = content.strip()
    if not s:
        return False
    if len(s) < 2 and s not in "{}[];":
        return False
    if _SINGLE_TOKEN_RE.match(s):
        return False
    if _KEYWORD_RE.match(s):
        return True
    if _CALL_RE.search(s):
        return True
    if _ASSIGN_RE.search(s):
        return True
    if _STRUCTURAL_RE.search(s):
        return True
    if _DECORATOR_RE.match(s):
        return True
    return False


def scan_file(path: Path, text: str) -> list[int]:
    """Return list of 1-based line numbers containing commented-out code."""
    prefix = _comment_prefix(path)
    if prefix is None:
        return []

    violations: list[int] = []
    # Build a regex that matches the comment marker, optional whitespace,
    # then captures the rest. Handles leading whitespace (indented code).
    marker_re = re.compile(
        r"^\s*" + re.escape(prefix) + r"(?!" + re.escape(prefix[0]) + r")\s*(.*)$"
    )

    for lineno, line in enumerate(text.splitlines(), start=1):
        m = marker_re.match(line)
        if not m:
            continue
        content = m.group(1)
        # Skip doc-comment prefixes
        full_comment = prefix + content
        if _DOC_RE.match(full_comment.lstrip()):
            continue
        if _looks_like_code(content):
            violations.append(lineno)
    return violations


def main() -> None:
    if "--self-check" in sys.argv:
        _lib.log("self-check OK")
        sys.exit(0)

    raw = sys.stdin.read()
    if not raw.strip():
        sys.exit(0)

    try:
        payload = json.loads(raw)
    except json.JSONDecodeError:
        sys.exit(0)

    # PostToolUse: tool_response contains the result; tool_input has the path.
    tool_input = (
        payload.get("tool_input")
        or payload.get("input")
        or {}
    ) if isinstance(payload, dict) else {}

    file_path_str = (
        tool_input.get("file_path")
        or tool_input.get("path")
        or ""
    )
    if not file_path_str:
        sys.exit(0)

    path = Path(file_path_str)
    if not path.is_file():
        sys.exit(0)

    try:
        text = path.read_text(encoding="utf-8", errors="replace")
    except OSError:
        sys.exit(0)

    violation_lines = scan_file(path, text)
    if violation_lines:
        lines_str = ", ".join(str(n) for n in violation_lines[:10])
        if len(violation_lines) > 10:
            lines_str += f" (+{len(violation_lines) - 10} more)"
        msg = (
            f"DEAD-CODE-VIOLATION: {path} has commented-out code "
            f"at line(s) {lines_str}"
        )
        _lib.log(msg)


if __name__ == "__main__":
    main()
code-qualitydead-codegovernanceclaude-code
Author convergent-systems-key. Catalog data license CC-BY-4.0.