Add librarian Stage 1 oversize check

Third Stage 1 piece: scripts/librarian_check_oversize.py walks
non-archive markdown files and flags any over the 500-line soft cap
declared in AGENTS.md. Wired into scripts/librarian.sh.

Caught one real finding on first run: docs/performance.md at 914
lines. Splitting it into sub-topic notes is a separate cleanup task
— surfaced for the user, not auto-applied.

Updates docs/plans/librarian.md Progress + sets the next concrete
step to a stale-plan checker.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-07 23:31:37 +09:00
co-authored by Claude Opus 4.7
parent 0b363b5031
commit 5a1b944931
3 changed files with 109 additions and 5 deletions
+11 -5
View File
@@ -134,6 +134,13 @@ operator applies the patch.
- ✅ Stage 1 orchestrator: `scripts/librarian.sh`. Runs every Stage 1 - ✅ Stage 1 orchestrator: `scripts/librarian.sh`. Runs every Stage 1
check in order, aggregates exit code, prints findings inline. Single check in order, aggregates exit code, prints findings inline. Single
entry point for users and (future) cron. entry point for users and (future) cron.
- ✅ AGENTS.md mentions `scripts/librarian.sh` as the doc-lint entry
point in "Notes For Future Agents" (commit `0b363b5`).
- ✅ Stage 1, piece 3: `scripts/librarian_check_oversize.py`. Flags
any non-archive markdown file over the 500-line soft cap declared
in AGENTS.md. Caught one real finding on first run:
`docs/performance.md` at 914 lines — split into sub-topics deferred
as a separate task.
## Stage 1 Remaining Checks ## Stage 1 Remaining Checks
@@ -142,11 +149,10 @@ operator applies the patch.
judgment is the open question). judgment is the open question).
- MEMORY.md drift (index lines vs target file `description:` frontmatter). - MEMORY.md drift (index lines vs target file `description:` frontmatter).
- Duplicate prose (high-overlap pairs across archive vs research). - Duplicate prose (high-overlap pairs across archive vs research).
- Oversize files (>500-line soft cap from AGENTS.md).
## Next Concrete Step ## Next Concrete Step
Mention `scripts/librarian.sh` in AGENTS.md so agents and humans know Stale plan check (`scripts/librarian_check_stale_plans.py`). Walks
it exists as the canonical Stage 1 entry point. After that, pick one `docs/plans/*.md` and flags files whose mtime is older than N days
of the remaining checks above to add as the next checker (oversize AND whose path hasn't appeared in `git log` over the same window.
files is the cheapest; stale plans is the most useful). This catches plans that drift out of mind without being archived.
+6
View File
@@ -21,6 +21,12 @@ if ! uv run python scripts/librarian_check_citations.py; then
fi fi
echo echo
echo "→ File size (500-line soft cap from AGENTS.md)"
if ! uv run python scripts/librarian_check_oversize.py; then
overall=1
fi
echo
if [ "$overall" -eq 0 ]; then if [ "$overall" -eq 0 ]; then
echo "All Stage 1 checks passed." echo "All Stage 1 checks passed."
else else
+92
View File
@@ -0,0 +1,92 @@
"""Oversize file check for librarian Stage 1.
Walks markdown files and flags any whose line count exceeds the
soft cap declared in AGENTS.md ("Docs & Experiment Workflow"
section). Skips read-only archive doc directories — splitting them
isn't an option.
Usage:
uv run python scripts/librarian_check_oversize.py
"""
from __future__ import annotations
import sys
from pathlib import Path
SOFT_CAP_LINES = 500
EXCLUDED_DIRS = {
".git",
".mypy_cache",
".pytest_cache",
".ruff_cache",
".venv",
"__pycache__",
"build",
"dist",
"node_modules",
"runs",
"target",
"tools",
"wheels",
}
EXCLUDED_DOC_PREFIXES = (
Path("docs/archive"),
Path("docs/plans/archive"),
)
def _repo_root() -> Path:
current = Path(__file__).resolve()
for parent in current.parents:
if (parent / "pyproject.toml").is_file():
return parent
raise RuntimeError("pyproject.toml을 찾을 수 없어 repository root를 판정할 수 없습니다.")
def _is_excluded(rel: Path) -> bool:
if any(part in EXCLUDED_DIRS for part in rel.parts):
return True
return any(prefix in rel.parents for prefix in EXCLUDED_DOC_PREFIXES)
def _markdown_files(root: Path) -> list[Path]:
files: list[Path] = []
for path in root.rglob("*.md"):
rel = path.relative_to(root)
if not _is_excluded(rel):
files.append(path)
return sorted(set(files))
def _line_count(path: Path) -> int:
with path.open(encoding="utf-8", errors="replace") as f:
return sum(1 for _ in f)
def main() -> int:
root = _repo_root()
files = _markdown_files(root)
if not files:
print("검사할 Markdown 파일이 없습니다.", file=sys.stderr)
return 1
over = [(path, _line_count(path)) for path in files]
over = [(p, n) for p, n in over if n > SOFT_CAP_LINES]
if not over:
print(f"All markdown files under {SOFT_CAP_LINES}-line soft cap.")
return 0
over.sort(key=lambda item: -item[1])
print(f"Files over {SOFT_CAP_LINES}-line soft cap:")
for path, n in over:
rel = path.relative_to(root)
print(f" {n:5d} {rel}")
return 1
if __name__ == "__main__":
raise SystemExit(main())