From dc795793911314959f1ce64d11f6bbc47ef1c80e Mon Sep 17 00:00:00 2001 From: Krzysztof kuhy Rudnicki Date: Fri, 14 Aug 2026 14:25:41 +0200 Subject: [PATCH] Add refactor_claude_todo.md: 250-line file-length cap A ready-to-use prompt describing the goal (every file at most 250 lines, code and prose alike), this repo's actual violations ranked by Fowler ROI (lines x commits/yr), how to split each kind, and the requirement to wire in the shared pre-commit gate so files stay small permanently. Generated from a measured survey of every repo; the gate itself lives in ~/utils (file_length/ + scripts/check_file_length.sh). Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01C7yZz45ezmzCB89Cv5gAtZ --- refactor_claude_todo.md | 95 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) create mode 100644 refactor_claude_todo.md diff --git a/refactor_claude_todo.md b/refactor_claude_todo.md new file mode 100644 index 0000000..00ab9ed --- /dev/null +++ b/refactor_claude_todo.md @@ -0,0 +1,95 @@ +# Refactor TODO — enforce the 250-line file cap + +> **This file is a ready-to-use prompt.** Paste it to Claude, or open this repo +> and say "do refactor_claude_todo". It is self-contained: everything needed to +> execute is below. Generated 2026-08-14 from a measured survey of every repo. + +## Goal + +Every file in this repo must be **at most 250 lines** — source, tests, and +prose (`.md`/`.txt`/`.rst`/`.tex`) alike — and must **stay** that way forever, +enforced by a gate that fails the commit, not by a note anyone can ignore. + +Why: a file that cannot be read in one piece forces re-reads and partial edits, +which is the single largest avoidable cost in an LLM-assisted workflow. Aim by +churn, not size alone — refactoring pays where code is read and changed often +(Fowler, *refactoring economic benefit*). + +## Scope in this repo + +- **71 files** currently exceed 250 lines (of 222 eligible files). +- **26,768 lines** sit in violation; longest file is **897 lines**. + +Exempt (do NOT split these): +- generated files — `*.g.dart`, `*.freezed.dart`, `*.gr.dart`, `**/l10n/generated/**`, + anything with a `GENERATED` header +- markup — `.html`, `.css`, `.scss` +- data files — `.json`, `.yaml`, `.csv`, wordlists and other data-ish `.txt` + (mean line length under 25 chars) + +## Violations, highest ROI first + +ROI = lines x commits in the last year. Work top-down; a long file nobody edits +has near-zero payoff and should not be first. + +| lines | commits/yr | kind | file | +|------:|-----------:|:-----|:-----| +| 390 | 48 | code | `screen_locker/screen_lock.py` | +| 771 | 12 | code | `stronglift_replacement/workout_app/lib/screens/settings_screen.dart` | +| 897 | 8 | code | `stronglift_replacement/workout_app/lib/screens/history_screen.dart` | +| 752 | 9 | code | `stronglift_replacement/workout_app/lib/services/storage_service.dart` | +| 319 | 21 | code | `screen_locker/tests/conftest.py` | +| 480 | 12 | code | `stronglift_replacement/workout_app/test/screens/settings_screen_test.dart` | +| 633 | 9 | code | `stronglift_replacement/workout_app/lib/screens/workout_screen.dart` | +| 343 | 16 | code | `screen_locker/tests/test_init_and_log.py` | +| 370 | 14 | code | `screen_locker/_ui_flows.py` | +| 377 | 12 | code | `screen_locker/_phone_verification.py` | +| 590 | 6 | code | `stronglift_replacement/workout_app/test/services/workout_sync_service_test.dart` | +| 256 | 13 | code | `screen_locker/tests/test_adb_and_phone.py` | +| 357 | 9 | code | `screen_locker/_manual_workout_dialog.py` | +| 384 | 8 | code | `screen_locker/_shutdown.py` | +| 714 | 4 | code | `stronglift_replacement/workout_app/test/services/storage_service_test.dart` | + +_(56 further files over 250 lines not listed — re-run the survey for the full set.)_ + +## How to split + +- **Python** — extract cohesive helpers into sibling modules; keep the public + API and imports stable. +- **Shell** — split into `lib/*.sh` sourced by a thin entry script. Keep + `set -euo pipefail` in each. +- **Dart / TypeScript** — extract widgets/components into their own files. +- **Tests** — split by test-group into sibling files + (`foo.test.ts` -> `foo.parsing.test.ts`, `foo.render.test.ts`). Coverage must + not drop. +- **Docs** — split into topic files under `docs/` with an index. For an + oversized `CLAUDE.md`, move detail into referenced docs so the + always-loaded part shrinks. + +**Do not** game the cap: no one-lining, no deleting tests, no moving code into +an exempt extension, no `# noqa`-style suppressions. + +## Make it permanent (required — this is the point) + +A refactor without a gate silently regrows. Before this task is done: + +1. Wire the shared gate `~/utils/scripts/check_file_length.sh` into this repo's + `.pre-commit-config.yaml` as a local hook. If the repo has no pre-commit + config, add a minimal one. +2. The hook checks **files in the commit** (not the whole tree), so unrelated + commits never break, and it **fails** — exit 1, not a warning. +3. No baseline file and no allowlist. Those are suppressions. +4. If this repo has CI (`.github/workflows`), add the same check there so it + also fails on push. + +## Done condition + +- `bash ~/utils/scripts/check_file_length.sh --all` from this repo root exits 0. +- The repo's own test suite and coverage bar are still green. +- `pre-commit run --files ` passes. +- A deliberately over-250-line test file, staged, makes `git commit` **fail**. +- For a deployed daemon/app: the entry point still actually runs. + +## Verify + +Run the suite, then actually run the locker entry point — this repo deploys from the working tree, so a broken import is a live outage.