mirror of
https://github.com/kuhyx/utils.git
synced 2026-11-10 20:27:15 +01:00
Three exemptions the first cut missed, all of them files no commit could ever fix: - Git-ignored files (todo's exported BACKLOG.md, logs, scratch dumps). These are rewritten wholesale by whatever produces them, so splitting one is meaningless, and pre-commit never sees them anyway. Applied to explicitly-named paths as well as --all, so the two modes cannot disagree about the same file. - Vendored agent-skill bundles (.agents/skills, .claude/skills). A SKILL.md is a fixed-name file with frontmatter; splitting one breaks the format. Matched by directory name wherever it appears, since these get dropped into any repo. - .utils, where CI checks out this repo. Without it a repo's lint job fails on utils' own long files, which that repo cannot fix. Baseline accordingly drops from 657 to 597. Re-verified all eight fixtures: over-cap fails, under-cap passes, generated/vendored/wordlist/git-ignored exempt, prose still capped, and the gate still passes on its own source. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C7yZz45ezmzCB89Cv5gAtZ
224 lines
6.2 KiB
Python
224 lines
6.2 KiB
Python
"""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)
|