"""Shared configuration for the 250-line file-length cap. Single source of truth for the cap and its exemptions, imported by both the pre-commit gate (`check.py`) and the survey. Duplicating these lists in shell is what lets a gate and a survey silently disagree. """ from __future__ import annotations import re from pathlib import Path #: Hard cap. Files above this fail the gate. Code and prose alike. MAX_LINES = 250 #: Extensions the cap applies to: real programming languages plus prose. #: Markup (.html/.css/.scss) is deliberately absent -- templates split into #: partials, not functions, so a line cap does not express anything useful. CAPPED_EXTENSIONS = { # code ".py", ".dart", ".ts", ".tsx", ".js", ".jsx", ".sh", ".bash", ".zsh", ".c", ".h", ".cpp", ".cc", ".hpp", ".go", ".rs", ".java", ".kt", ".kts", ".swift", ".rb", ".lua", ".sql", ".vue", ".cs", ".php", # prose ".md", ".markdown", ".txt", ".rst", ".tex", ".org", } #: Generated files: regenerated by a tool, so splitting them is meaningless #: (the tool would just rewrite the original next run). GENERATED_PATTERN = re.compile( r"\.g\.dart$" r"|\.freezed\.dart$" r"|\.gr\.dart$" r"|\.config\.dart$" r"|\.mocks\.dart$" r"|\.min\.(js|css)$" r"|\.pb\.(go|py)$" r"|_pb2(_grpc)?\.py$" r"|\.generated\.ts$" r"|\.d\.ts$" r"|/l10n/generated/" r"|/generated/" ) #: A marker in the first few lines also marks a file generated. Covers tools #: that do not use a distinguishing extension. GENERATED_MARKERS = ( "GENERATED CODE - DO NOT MODIFY", "@generated", "DO NOT EDIT", "Autogenerated", "AUTOGENERATED", "generated by", ) #: Directory names never descended into: dependencies, build output, caches. EXCLUDED_DIRS = frozenset( { ".git", "node_modules", ".dart_tool", "build", ".venv", "venv", "env", "site-packages", "target", "ephemeral", "__pycache__", ".mypy_cache", ".pytest_cache", ".ruff_cache", "dist", ".next", "vendor", "third_party", "Pods", ".gradle", ".idea", ".cxx", "coverage", "htmlcov", ".tox", "Library", "Temp", "obj", ".terraform", ".fvm", ".angular", ".cache", ".svelte-kit", "migrations", ".ci-mirror-venv", ".hippo", ".build-deps", # Where CI checks out the shared gate itself (kuhyx/utils). Without # this, a repo's lint job fails on utils' own long files -- a gate # that fails for reasons the repo being gated cannot fix. ".utils", } ) #: Third-party content living *inside* a repo you own, keyed by repo dir name. #: Verified by git remote / vendor manifest, not guessed. VENDORED_SUBPATHS = { "mcp-servers": ("servers",), "dufs-cloud": ("app/assets/pdfjs", "web/assets/pdfjs"), ".claude": ( "projects", "shell-snapshots", "cache", "backups", "todos", "statsig", "history", "chrome", "downloads", "plugins", "ide", ), # Agent skill bundles vendored from Anthropic (todo: commit f91311f). # A SKILL.md is a fixed-name file with frontmatter; detail belongs in a # sibling references/ dir, so splitting one into SKILL.part2.md would # break the format rather than improve readability. "todo": (".agents/skills",), } #: Directory names that mark a vendored agent-skill tree wherever they appear. #: Keyed by name rather than repo because these bundles get dropped into any #: repo that wants them, not just the one they were first seen in. VENDORED_ANYWHERE = ("/.agents/skills/", "/.claude/skills/") #: Whole repos under ~ that are third-party clones, not kuhy's work. #: Verified 2026-08-14 by `git remote get-url origin` + commit authors. THIRD_PARTY_REPOS = frozenset( { "StringTheory", # github.com/AnthonySf/TheStringTheory "reaper-mcp", # github.com/TwelveTake-Studios/reaper-mcp "freesound-mcp", # github.com/timjrobinson/FreesoundMCPServer "llama_cpp_dart_local", # generated FFI bindings "aseprite-mcp", "aur", "build-your-own-x", "build_your_x", "byox-ladder", "warriorjs", "untools", "awesome-mcp-explorer", "runnerup", "depotdownloader", "actions-runner", } ) #: A `.txt` whose mean line is shorter than this is data (a wordlist, a #: coordinate dump), not prose, so the cap does not apply. DATA_TXT_MAX_MEAN_LINE = 25 def is_generated(path: Path) -> bool: """True if the file is tool-generated and must not be split by hand.""" if GENERATED_PATTERN.search(path.as_posix()): return True try: with path.open("r", encoding="utf-8", errors="replace") as handle: head = "".join(next(handle, "") for _ in range(5)) except OSError: return False return any(marker in head for marker in GENERATED_MARKERS) def is_data_text(path: Path, lines: int, size: int) -> bool: """True for wordlist-style .txt: many lines, very short ones.""" if path.suffix.lower() != ".txt" or lines <= 0: return False return (size / lines) < DATA_TXT_MAX_MEAN_LINE def is_vendored(path: Path) -> bool: """True if the path sits under a known vendored subtree or excluded dir.""" parts = path.parts if any(part in EXCLUDED_DIRS for part in parts): return True # Pre-commit passes paths relative to the repo root ('.agents/skills/x'), # while --all walks absolute ones ('/home/kuhy/todo/.agents/skills/x'). # Compare against a leading-slash form so one rule covers both modes. posix = path.as_posix() anchored = posix if posix.startswith("/") else f"/{posix}" if any(marker in anchored for marker in VENDORED_ANYWHERE): return True for repo, subs in VENDORED_SUBPATHS.items(): for sub in subs: if f"/{repo}/{sub}/" in posix or posix.startswith(f"{repo}/{sub}/"): return True return any(f"/{repo}/" in posix for repo in THIRD_PARTY_REPOS)