mirror of
https://github.com/kuhyx/utils.git
synced 2026-11-10 19:07:17 +01:00
Skip git-ignored files in the length gate, and vendored skill bundles
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
This commit is contained in:
parent
c54211f112
commit
ca81e091a1
File diff suppressed because it is too large
Load Diff
@ -11,6 +11,7 @@ from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
@ -61,14 +62,45 @@ def exempt_reason(path: Path, lines: int) -> str | None:
|
||||
return None
|
||||
|
||||
|
||||
def git_ignored(paths: list[Path], root: Path) -> set[Path]:
|
||||
"""The subset of `paths` that git ignores, empty if root is not a repo.
|
||||
|
||||
Git-ignored files are build output or generated data the repo does not
|
||||
own -- an app's own export (todo's BACKLOG.md), a log, a scratch dump.
|
||||
They are rewritten wholesale by whatever produces them, so splitting one
|
||||
is meaningless: the next run restores the original. Pre-commit never sees
|
||||
them either (they cannot be staged), so skipping them makes --all agree
|
||||
with the hook instead of failing on files no commit could ever fix.
|
||||
"""
|
||||
if not paths:
|
||||
return set()
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["git", "-C", str(root), "check-ignore", "--stdin"],
|
||||
input="\n".join(str(p) for p in paths),
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=False,
|
||||
)
|
||||
except OSError: # git not installed
|
||||
return set()
|
||||
# 0 = some ignored, 1 = none ignored, 128 = not a repo.
|
||||
if result.returncode not in (0, 1):
|
||||
return set()
|
||||
return {Path(line) for line in result.stdout.splitlines() if line}
|
||||
|
||||
|
||||
def iter_all(root: Path):
|
||||
"""Every file under root, skipping excluded directories."""
|
||||
"""Every file under root, skipping excluded and git-ignored files."""
|
||||
found: list[Path] = []
|
||||
for dirpath, dirnames, filenames in os.walk(root, followlinks=False):
|
||||
dirnames[:] = [d for d in dirnames if d not in EXCLUDED_DIRS]
|
||||
for name in filenames:
|
||||
candidate = Path(dirpath) / name
|
||||
if not candidate.is_symlink():
|
||||
yield candidate
|
||||
found.append(candidate)
|
||||
ignored = git_ignored(found, root)
|
||||
return [path for path in found if path not in ignored]
|
||||
|
||||
|
||||
def main() -> int:
|
||||
@ -89,7 +121,13 @@ def main() -> int:
|
||||
if args.all:
|
||||
targets = list(iter_all(Path.cwd()))
|
||||
elif args.paths:
|
||||
targets = args.paths
|
||||
# Explicitly-named paths get the same git-ignore skip as --all, so
|
||||
# naming a file directly cannot report a violation the hook never
|
||||
# would. Without this the two modes disagree on exactly the files
|
||||
# no commit could ever fix (todo's exported BACKLOG.md, logs).
|
||||
existing = [p for p in args.paths if p.is_file()]
|
||||
ignored = git_ignored(existing, Path.cwd())
|
||||
targets = [p for p in existing if p not in ignored]
|
||||
else:
|
||||
parser.error("give file paths or --all")
|
||||
|
||||
@ -118,9 +156,7 @@ def main() -> int:
|
||||
for path, lines in sorted(violations, key=lambda item: -item[1]):
|
||||
over = lines - MAX_LINES
|
||||
print(f" {path}: {lines} lines (over by {over})", file=sys.stderr)
|
||||
print(
|
||||
"\nSplit them, or see refactor_claude_todo.md in this repo.", file=sys.stderr
|
||||
)
|
||||
print("\nSplit them, or see refactor_claude_todo.md in this repo.", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
|
||||
|
||||
@ -121,6 +121,10 @@ EXCLUDED_DIRS = frozenset(
|
||||
".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",
|
||||
}
|
||||
)
|
||||
|
||||
@ -142,8 +146,18 @@ VENDORED_SUBPATHS = {
|
||||
"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(
|
||||
@ -195,7 +209,13 @@ def is_vendored(path: Path) -> bool:
|
||||
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}/"):
|
||||
|
||||
Loading…
Reference in New Issue
Block a user