Protokoll #33368

ID33368
Zeitstempel2026-04-21 01:55:47.579149
Clientroot
IP62.246.34.195
Modellclaude-sonnet-4-20250514
Statuscompleted
Tokens1,749 (Input: 869, Output: 880)
Dauer77 ms
Request-Zeit2026-04-21 01:55:47.579149
Response-Zeit2026-04-21 01:55:47.655995

Request

{
    "event": "PreToolUse",
    "tool_name": "Write",
    "tool_input": {
        "file_path": "\/var\/www\/dev.campus.systemische-tools.de\/crawler\/scripts\/htmx_lint.py",
        "content": "\"\"\"HTMX-compliance linter — enforces rules HTMX-C1..C5 from CLAUDE.md.\n\nRules:\n- HTMX-C1: hx-post    MUST have hx-headers containing X-CSRF-TOKEN\n- HTMX-C2: hx-delete  MUST have hx-headers containing X-CSRF-TOKEN\n- HTMX-C3: hx-patch   MUST have hx-headers containing X-CSRF-TOKEN\n- HTMX-C4: hx-delete  MUST have hx-confirm\n- HTMX-C5: hx-put     MUST have hx-headers containing X-CSRF-TOKEN\n\nUsage:\n    python scripts\/htmx_lint.py <path> [<path> ...]\n\nExit codes:\n    0  OK\n    1  violation(s)\n\"\"\"\nfrom __future__ import annotations\n\nimport pathlib\nimport re\nimport sys\nfrom dataclasses import dataclass\nfrom typing import Iterable\n\nMUTATING_VERBS = (\"post\", \"put\", \"patch\", \"delete\")\nTEMPLATE_GLOBS = (\"*.php\", \"*.phtml\", \"*.html\", \"*.tpl\")\n\n\n@dataclass(frozen=True)\nclass Finding:\n    file: pathlib.Path\n    lineno: int\n    rule: str\n    message: str\n\n\n_TAG_RE = re.compile(r\"<[^>]*\\bhx-(post|put|patch|delete)\\b[^>]*>\", re.IGNORECASE | re.DOTALL)\n\n\ndef _iter_template_files(paths: Iterable[str]) -> Iterable[pathlib.Path]:\n    for raw in paths:\n        base = pathlib.Path(raw)\n        if base.is_file():\n            yield base\n        elif base.is_dir():\n            for pattern in TEMPLATE_GLOBS:\n                yield from base.rglob(pattern)\n\n\ndef _check_tag(tag: str) -> list[str]:\n    lowered = tag.lower()\n    verb_match = re.search(r\"\\bhx-(post|put|patch|delete)\\b\", lowered)\n    if not verb_match:\n        return []\n    verb = verb_match.group(1)\n    failures: list[str] = []\n    has_csrf = \"hx-headers\" in lowered and \"x-csrf-token\" in lowered\n    if verb == \"post\" and not has_csrf:\n        failures.append(\"HTMX-C1: hx-post without X-CSRF-TOKEN\")\n    if verb == \"patch\" and not has_csrf:\n        failures.append(\"HTMX-C3: hx-patch without X-CSRF-TOKEN\")\n    if verb == \"put\" and not has_csrf:\n        failures.append(\"HTMX-C5: hx-put without X-CSRF-TOKEN\")\n    if verb == \"delete\":\n        if not has_csrf:\n            failures.append(\"HTMX-C2: hx-delete without X-CSRF-TOKEN\")\n        if \"hx-confirm\" not in lowered:\n            failures.append(\"HTMX-C4: hx-delete without hx-confirm\")\n    return failures\n\n\ndef _lineno_of(text: str, offset: int) -> int:\n    return text.count(\"\\n\", 0, offset) + 1\n\n\ndef check_file(path: pathlib.Path) -> list[Finding]:\n    findings: list[Finding] = []\n    try:\n        text = path.read_text(encoding=\"utf-8\")\n    except UnicodeDecodeError:\n        return findings\n    for match in _TAG_RE.finditer(text):\n        tag = match.group(0)\n        for fail in _check_tag(tag):\n            rule_id, _, message = fail.partition(\":\")\n            findings.append(Finding(path, _lineno_of(text, match.start()), rule_id, message.strip()))\n    return findings\n\n\ndef main(argv: list[str]) -> int:\n    if not argv:\n        print(\"usage: htmx_lint.py <path> [...]\", file=sys.stderr)\n        return 1\n    findings: list[Finding] = []\n    for path in _iter_template_files(argv):\n        findings.extend(check_file(path))\n    for f in findings:\n        print(f\"{f.file}:{f.lineno}: {f.rule}: {f.message}\", file=sys.stderr)\n    return 1 if findings 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\/htmx_lint.py",
        "content": "\"\"\"HTMX-compliance linter — enforces rules HTMX-C1..C5 from CLAUDE.md.\n\nRules:\n- HTMX-C1: hx-post    MUST have hx-headers containing X-CSRF-TOKEN\n- HTMX-C2: hx-delete  MUST have hx-headers containing X-CSRF-TOKEN\n- HTMX-C3: hx-patch   MUST have hx-headers containing X-CSRF-TOKEN\n- HTMX-C4: hx-delete  MUST have hx-confirm\n- HTMX-C5: hx-put     MUST have hx-headers containing X-CSRF-TOKEN\n\nUsage:\n    python scripts\/htmx_lint.py <path> [<path> ...]\n\nExit codes:\n    0  OK\n    1  violation(s)\n\"\"\"\nfrom __future__ import annotations\n\nimport pathlib\nimport re\nimport sys\nfrom dataclasses import dataclass\nfrom typing import Iterable\n\nMUTATING_VERBS = (\"post\", \"put\", \"patch\", \"delete\")\nTEMPLATE_GLOBS = (\"*.php\", \"*.phtml\", \"*.html\", \"*.tpl\")\n\n\n@dataclass(frozen=True)\nclass Finding:\n    file: pathlib.Path\n    lineno: int\n    rule: str\n    message: str\n\n\n_TAG_RE = re.compile(r\"<[^>]*\\bhx-(post|put|patch|delete)\\b[^>]*>\", re.IGNORECASE | re.DOTALL)\n\n\ndef _iter_template_files(paths: Iterable[str]) -> Iterable[pathlib.Path]:\n    for raw in paths:\n        base = pathlib.Path(raw)\n        if base.is_file():\n            yield base\n        elif base.is_dir():\n            for pattern in TEMPLATE_GLOBS:\n                yield from base.rglob(pattern)\n\n\ndef _check_tag(tag: str) -> list[str]:\n    lowered = tag.lower()\n    verb_match = re.search(r\"\\bhx-(post|put|patch|delete)\\b\", lowered)\n    if not verb_match:\n        return []\n    verb = verb_match.group(1)\n    failures: list[str] = []\n    has_csrf = \"hx-headers\" in lowered and \"x-csrf-token\" in lowered\n    if verb == \"post\" and not has_csrf:\n        failures.append(\"HTMX-C1: hx-post without X-CSRF-TOKEN\")\n    if verb == \"patch\" and not has_csrf:\n        failures.append(\"HTMX-C3: hx-patch without X-CSRF-TOKEN\")\n    if verb == \"put\" and not has_csrf:\n        failures.append(\"HTMX-C5: hx-put without X-CSRF-TOKEN\")\n    if verb == \"delete\":\n        if not has_csrf:\n            failures.append(\"HTMX-C2: hx-delete without X-CSRF-TOKEN\")\n        if \"hx-confirm\" not in lowered:\n            failures.append(\"HTMX-C4: hx-delete without hx-confirm\")\n    return failures\n\n\ndef _lineno_of(text: str, offset: int) -> int:\n    return text.count(\"\\n\", 0, offset) + 1\n\n\ndef check_file(path: pathlib.Path) -> list[Finding]:\n    findings: list[Finding] = []\n    try:\n        text = path.read_text(encoding=\"utf-8\")\n    except UnicodeDecodeError:\n        return findings\n    for match in _TAG_RE.finditer(text):\n        tag = match.group(0)\n        for fail in _check_tag(tag):\n            rule_id, _, message = fail.partition(\":\")\n            findings.append(Finding(path, _lineno_of(text, match.start()), rule_id, message.strip()))\n    return findings\n\n\ndef main(argv: list[str]) -> int:\n    if not argv:\n        print(\"usage: htmx_lint.py <path> [...]\", file=sys.stderr)\n        return 1\n    findings: list[Finding] = []\n    for path in _iter_template_files(argv):\n        findings.extend(check_file(path))\n    for f in findings:\n        print(f\"{f.file}:{f.lineno}: {f.rule}: {f.message}\", file=sys.stderr)\n    return 1 if findings 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 →