Protokoll #33366

ID33366
Zeitstempel2026-04-21 01:55:17.805923
Clientroot
IP62.246.34.195
Modellclaude-sonnet-4-20250514
Statuscompleted
Tokens1,989 (Input: 989, Output: 1,000)
Dauer63 ms
Request-Zeit2026-04-21 01:55:17.805923
Response-Zeit2026-04-21 01:55:17.868896

Request

{
    "event": "PreToolUse",
    "tool_name": "Write",
    "tool_input": {
        "file_path": "\/var\/www\/dev.campus.systemische-tools.de\/crawler\/scripts\/max_loc_linter.py",
        "content": "\"\"\"MaxLOC linter — enforces 80-LOC hard limit for methods and classes.\n\nCount excludes:\n- blank lines\n- comment-only lines (# ...)\n- docstring lines (first string expression of a function\/class body)\n\nUsage:\n    python scripts\/max_loc_linter.py <path> [<path> ...]\n\nExit codes:\n    0  OK\n    1  violation(s) detected\n    2  parse error\n\"\"\"\nfrom __future__ import annotations\n\nimport ast\nimport pathlib\nimport sys\nfrom dataclasses import dataclass\nfrom typing import Iterable\n\nHARD_LIMIT_METHOD = 80\nHARD_LIMIT_CLASS = 80\nSOFT_LIMIT = 50\n\n\n@dataclass(frozen=True)\nclass Violation:\n    file: pathlib.Path\n    kind: str\n    name: str\n    lineno: int\n    loc: int\n    limit: int\n\n\ndef _iter_py_files(paths: Iterable[str]) -> Iterable[pathlib.Path]:\n    for raw in paths:\n        base = pathlib.Path(raw)\n        if base.is_file() and base.suffix == \".py\":\n            yield base\n        elif base.is_dir():\n            yield from (\n                p for p in base.rglob(\"*.py\") if \"venv\" not in p.parts and \".venv\" not in p.parts\n            )\n\n\ndef _source_lines(src: str) -> list[str]:\n    return src.splitlines()\n\n\ndef _count_loc(node: ast.AST, lines: list[str]) -> int:\n    start = node.lineno\n    end = getattr(node, \"end_lineno\", start)\n    docstring_span = _docstring_span(node)\n    counted = 0\n    for lineno in range(start, end + 1):\n        if docstring_span and docstring_span[0] <= lineno <= docstring_span[1]:\n            continue\n        text = lines[lineno - 1].strip()\n        if not text or text.startswith(\"#\"):\n            continue\n        counted += 1\n    return counted\n\n\ndef _docstring_span(node: ast.AST) -> tuple[int, int] | None:\n    body = getattr(node, \"body\", None)\n    if not body:\n        return None\n    first = body[0]\n    if not isinstance(first, ast.Expr) or not isinstance(first.value, ast.Constant):\n        return None\n    if not isinstance(first.value.value, str):\n        return None\n    return (first.lineno, getattr(first, \"end_lineno\", first.lineno))\n\n\ndef _check_tree(path: pathlib.Path, tree: ast.AST, lines: list[str]) -> list[Violation]:\n    violations: list[Violation] = []\n    for node in ast.walk(tree):\n        if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):\n            loc = _count_loc(node, lines)\n            if loc > HARD_LIMIT_METHOD:\n                violations.append(\n                    Violation(path, \"method\", node.name, node.lineno, loc, HARD_LIMIT_METHOD),\n                )\n        elif isinstance(node, ast.ClassDef):\n            loc = _count_loc(node, lines)\n            if loc > HARD_LIMIT_CLASS:\n                violations.append(\n                    Violation(path, \"class\", node.name, node.lineno, loc, HARD_LIMIT_CLASS),\n                )\n    return violations\n\n\ndef check_file(path: pathlib.Path) -> list[Violation]:\n    src = path.read_text(encoding=\"utf-8\")\n    tree = ast.parse(src, filename=str(path))\n    return _check_tree(path, tree, _source_lines(src))\n\n\ndef main(argv: list[str]) -> int:\n    if not argv:\n        print(\"usage: max_loc_linter.py <path> [...]\", file=sys.stderr)\n        return 2\n    all_violations: list[Violation] = []\n    for path in _iter_py_files(argv):\n        try:\n            all_violations.extend(check_file(path))\n        except SyntaxError as exc:\n            print(f\"PARSE_ERROR {path}: {exc}\", file=sys.stderr)\n            return 2\n    for v in all_violations:\n        print(\n            f\"{v.file}:{v.lineno}: {v.kind} '{v.name}' has {v.loc} LOC (hard limit {v.limit})\",\n            file=sys.stderr,\n        )\n    return 1 if all_violations else 0\n\n\nif __name__ == \"__main__\":\n    sys.exit(main(sys.argv[1:]))\n"
    }
}

Response

{
    "tool_response": {
        "type": "create",
        "filePath": "\/var\/www\/dev.campus.systemische-tools.de\/crawler\/scripts\/max_loc_linter.py",
        "content": "\"\"\"MaxLOC linter — enforces 80-LOC hard limit for methods and classes.\n\nCount excludes:\n- blank lines\n- comment-only lines (# ...)\n- docstring lines (first string expression of a function\/class body)\n\nUsage:\n    python scripts\/max_loc_linter.py <path> [<path> ...]\n\nExit codes:\n    0  OK\n    1  violation(s) detected\n    2  parse error\n\"\"\"\nfrom __future__ import annotations\n\nimport ast\nimport pathlib\nimport sys\nfrom dataclasses import dataclass\nfrom typing import Iterable\n\nHARD_LIMIT_METHOD = 80\nHARD_LIMIT_CLASS = 80\nSOFT_LIMIT = 50\n\n\n@dataclass(frozen=True)\nclass Violation:\n    file: pathlib.Path\n    kind: str\n    name: str\n    lineno: int\n    loc: int\n    limit: int\n\n\ndef _iter_py_files(paths: Iterable[str]) -> Iterable[pathlib.Path]:\n    for raw in paths:\n        base = pathlib.Path(raw)\n        if base.is_file() and base.suffix == \".py\":\n            yield base\n        elif base.is_dir():\n            yield from (\n                p for p in base.rglob(\"*.py\") if \"venv\" not in p.parts and \".venv\" not in p.parts\n            )\n\n\ndef _source_lines(src: str) -> list[str]:\n    return src.splitlines()\n\n\ndef _count_loc(node: ast.AST, lines: list[str]) -> int:\n    start = node.lineno\n    end = getattr(node, \"end_lineno\", start)\n    docstring_span = _docstring_span(node)\n    counted = 0\n    for lineno in range(start, end + 1):\n        if docstring_span and docstring_span[0] <= lineno <= docstring_span[1]:\n            continue\n        text = lines[lineno - 1].strip()\n        if not text or text.startswith(\"#\"):\n            continue\n        counted += 1\n    return counted\n\n\ndef _docstring_span(node: ast.AST) -> tuple[int, int] | None:\n    body = getattr(node, \"body\", None)\n    if not body:\n        return None\n    first = body[0]\n    if not isinstance(first, ast.Expr) or not isinstance(first.value, ast.Constant):\n        return None\n    if not isinstance(first.value.value, str):\n        return None\n    return (first.lineno, getattr(first, \"end_lineno\", first.lineno))\n\n\ndef _check_tree(path: pathlib.Path, tree: ast.AST, lines: list[str]) -> list[Violation]:\n    violations: list[Violation] = []\n    for node in ast.walk(tree):\n        if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):\n            loc = _count_loc(node, lines)\n            if loc > HARD_LIMIT_METHOD:\n                violations.append(\n                    Violation(path, \"method\", node.name, node.lineno, loc, HARD_LIMIT_METHOD),\n                )\n        elif isinstance(node, ast.ClassDef):\n            loc = _count_loc(node, lines)\n            if loc > HARD_LIMIT_CLASS:\n                violations.append(\n                    Violation(path, \"class\", node.name, node.lineno, loc, HARD_LIMIT_CLASS),\n                )\n    return violations\n\n\ndef check_file(path: pathlib.Path) -> list[Violation]:\n    src = path.read_text(encoding=\"utf-8\")\n    tree = ast.parse(src, filename=str(path))\n    return _check_tree(path, tree, _source_lines(src))\n\n\ndef main(argv: list[str]) -> int:\n    if not argv:\n        print(\"usage: max_loc_linter.py <path> [...]\", file=sys.stderr)\n        return 2\n    all_violations: list[Violation] = []\n    for path in _iter_py_files(argv):\n        try:\n            all_violations.extend(check_file(path))\n        except SyntaxError as exc:\n            print(f\"PARSE_ERROR {path}: {exc}\", file=sys.stderr)\n            return 2\n    for v in all_violations:\n        print(\n            f\"{v.file}:{v.lineno}: {v.kind} '{v.name}' has {v.loc} LOC (hard limit {v.limit})\",\n            file=sys.stderr,\n        )\n    return 1 if all_violations else 0\n\n\nif __name__ == \"__main__\":\n    sys.exit(main(sys.argv[1:]))\n",
        "structuredPatch": [],
        "originalFile": null,
        "userModified": false
    }
}
← Vorheriger Zur Liste Nächster →