Protokoll #33470

ID33470
Zeitstempel2026-04-21 02:28:47.482051
Clientroot
IP62.246.34.195
Modellclaude-sonnet-4-20250514
Statuscompleted
Tokens1,194 (Input: 94, Output: 1,100)
Dauer64 ms
Request-Zeit2026-04-21 02:28:47.482051
Response-Zeit2026-04-21 02:28:47.546363

Request

{
    "event": "PreToolUse",
    "tool_name": "Edit",
    "tool_input": {
        "file_path": "\/var\/www\/dev.campus.systemische-tools.de\/crawler\/scripts\/max_loc_linter.py",
        "old_string": "\"\"\"MaxLOC linter — enforces 80-LOC hard limit for methods and classes.",
        "new_string": "#!\/usr\/bin\/env python3\n\"\"\"MaxLOC linter — enforces 80-LOC hard limit for methods and classes.",
        "replace_all": false
    }
}

Response

{
    "tool_response": {
        "filePath": "\/var\/www\/dev.campus.systemische-tools.de\/crawler\/scripts\/max_loc_linter.py",
        "oldString": "\"\"\"MaxLOC linter — enforces 80-LOC hard limit for methods and classes.",
        "newString": "#!\/usr\/bin\/env python3\n\"\"\"MaxLOC linter — enforces 80-LOC hard limit for methods and classes.",
        "originalFile": "\"\"\"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 collections.abc import Iterable\nfrom dataclasses import dataclass\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": [
            {
                "oldStart": 1,
                "oldLines": 3,
                "newStart": 1,
                "newLines": 4,
                "lines": [
                    "+#!\/usr\/bin\/env python3",
                    " \"\"\"MaxLOC linter — enforces 80-LOC hard limit for methods and classes.",
                    " ",
                    " Count excludes:"
                ]
            }
        ],
        "userModified": false,
        "replaceAll": false
    }
}
← Vorheriger Zur Liste Nächster →