mirror of
https://github.com/kuhyx/utils.git
synced 2026-11-10 19:07:17 +01:00
Exempt frozen session artifacts from the 250-line cap
An implementation plan under docs/superpowers/plans/ and an append-only log under docs/superpowers/sessions/ are frozen records of one past session. They are read whole or not at all, so splitting one rewrites a historical record for no reading benefit -- the same reason generated files are exempt. Scoped deliberately: specs/ and contracts/ stay capped, because those are living documents that get re-read and edited. is_session_artifact() is kept separate from is_vendored() so the gate reports an accurate reason -- these are kuhy's own files, not third-party code. Relaxes the gate only, so no already-wired repo can newly fail. Verified: ~/todo still exits 0 on --all; testsAndMisc drops 198 -> 197 violations (plans/ exempt, specs/ still caught) in both absolute and relative modes. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
This commit is contained in:
parent
682eaf6f8c
commit
f8dded33c7
@ -11,19 +11,20 @@ from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import os
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
if __package__ in (None, ""): # invoked as a plain script
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
||||
|
||||
from file_length.config import ( # noqa: E402
|
||||
from file_length.config import (
|
||||
CAPPED_EXTENSIONS,
|
||||
EXCLUDED_DIRS,
|
||||
MAX_LINES,
|
||||
is_data_text,
|
||||
is_generated,
|
||||
is_session_artifact,
|
||||
is_vendored,
|
||||
)
|
||||
|
||||
@ -45,12 +46,30 @@ def count_lines(path: Path) -> int | None:
|
||||
return None
|
||||
|
||||
|
||||
def absolutize(path: Path) -> Path:
|
||||
"""`path` anchored at the cwd, without resolving symlinks.
|
||||
|
||||
The exemption rules are written against full paths ('/.claude/skills/'),
|
||||
but pre-commit passes paths relative to the repo root ('skills/x.md') while
|
||||
--all walks absolute ones. Matching a relative path against those rules
|
||||
silently drops the repo-name context, so the same file passed the gate one
|
||||
way and failed it the other.
|
||||
|
||||
Deliberately NOT `Path.resolve()`: build and asset dirs here are symlinks
|
||||
out to ../testsAndMisc_builds/, and resolving would move those paths
|
||||
outside the repo and change which exemptions match.
|
||||
"""
|
||||
return path if path.is_absolute() else Path.cwd() / path
|
||||
|
||||
|
||||
def exempt_reason(path: Path, lines: int) -> str | None:
|
||||
"""Why this file is not subject to the cap, or None if it is subject."""
|
||||
if path.suffix.lower() not in CAPPED_EXTENSIONS:
|
||||
return "not a capped extension"
|
||||
if is_vendored(path):
|
||||
if is_vendored(absolutize(path)):
|
||||
return "vendored / third-party"
|
||||
if is_session_artifact(absolutize(path)):
|
||||
return "frozen session artifact"
|
||||
if is_generated(path):
|
||||
return "generated"
|
||||
try:
|
||||
|
||||
@ -7,8 +7,8 @@ is what lets a gate and a survey silently disagree.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from pathlib import Path
|
||||
import re
|
||||
|
||||
#: Hard cap. Files above this fail the gate. Code and prose alike.
|
||||
MAX_LINES = 250
|
||||
@ -158,6 +158,18 @@ VENDORED_SUBPATHS = {
|
||||
#: repo that wants them, not just the one they were first seen in.
|
||||
VENDORED_ANYWHERE = ("/.agents/skills/", "/.claude/skills/")
|
||||
|
||||
#: Frozen records of past agent sessions: an implementation plan or an
|
||||
#: append-only session log describes what was done at one moment. Splitting one
|
||||
#: rewrites a historical record for no reading benefit -- nobody navigates them,
|
||||
#: they are read whole or not at all. Same category as generated files.
|
||||
#:
|
||||
#: Deliberately NOT the whole of docs/superpowers/: `specs/` and `contracts/`
|
||||
#: stay capped, because those are living documents that get re-read and edited.
|
||||
SESSION_ARTIFACTS_ANYWHERE = (
|
||||
"/docs/superpowers/plans/",
|
||||
"/docs/superpowers/sessions/",
|
||||
)
|
||||
|
||||
#: 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(
|
||||
@ -205,19 +217,35 @@ def is_data_text(path: Path, lines: int, size: int) -> bool:
|
||||
|
||||
|
||||
def is_vendored(path: Path) -> bool:
|
||||
"""True if the path sits under a known vendored subtree or excluded dir."""
|
||||
"""True if the path sits under a known vendored subtree or excluded dir.
|
||||
|
||||
Callers must pass an absolute path (see `check.absolutize`). Every rule
|
||||
below needs the repo-name context that a repo-relative path has already
|
||||
thrown away: '/.claude/skills/' cannot match 'skills/x.md', so a relative
|
||||
path would quietly skip the exemption instead of applying it.
|
||||
"""
|
||||
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):
|
||||
if any(marker in posix 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}/"):
|
||||
if f"/{repo}/{sub}/" in posix:
|
||||
return True
|
||||
return any(f"/{repo}/" in posix for repo in THIRD_PARTY_REPOS)
|
||||
|
||||
|
||||
def is_session_artifact(path: Path) -> bool:
|
||||
"""True for a frozen agent-session record (a plan or a session log).
|
||||
|
||||
Kept separate from `is_vendored` so the gate reports why a file is exempt:
|
||||
these are kuhy's own files, not third-party code.
|
||||
|
||||
Callers must pass an absolute path (see `check.absolutize`), for the same
|
||||
reason `is_vendored` does -- a repo-relative path has already dropped the
|
||||
leading directory these markers match on.
|
||||
"""
|
||||
posix = path.as_posix()
|
||||
return any(marker in posix for marker in SESSION_ARTIFACTS_ANYWHERE)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user