Commit Graph

249 Commits

Author SHA1 Message Date
72e1b6a8b1 block_compulsive_opening: test the allow/block decision
21 assertions for cco_state.sh, the lib that decides whether a launch is
permitted. Coverage measured: 62/66 lines = 93.94%. The only uncovered lines
are kill_app's pkill/pkill -9 pair, which must never run in a test on a
machine whose apps are live. cco_wrapper/cco_install/cco_report stay at 0%:
every function in them moves system binaries, writes /etc/pacman.d/hooks or
kills processes.

The assertion that matters most is the one in the negative direction: a
PREVIOUS hour's stamp must NOT keep blocking. If that decision inverted, a
legitimately-closed app could never be reopened, and the failure would look
like the tool working. Both directions are pinned, plus the empty-stamp case
and stale suspend-marker cleanup.

Also generalises run_all.sh from test_leechblock_*.sh to test_*.sh. This
directory now holds suites for more than one entry script, and a prefix glob
silently skips every suite added after it — including inside the CI discovery
step added in fbec4656, which runs this file.

The harness asserts its own fixture is complete rather than letting a typo in
a global surface as a confusing unbound-variable from inside the code under
test. That check is also what makes the fixture's globals legible to
shellcheck, which cannot see them being read across the source boundary — a
real check that happens to remove SC2034, not a suppression.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F3PzGc75gBcdxP3PkHXGcV
2026-08-21 15:53:54 +02:00
f5edebae4a block_compulsive_opening: split into lib/ under the 250-line cap
705 -> 182 lines, with state, the launch gate, install/uninstall and
reporting moved into cco_state.sh, cco_wrapper.sh, cco_install.sh and
cco_report.sh.

The entry script keeps its exact deployed path and stays a real file
there, not a symlink: the app wrappers already installed on disk
hard-code /usr/local/bin/block-compulsive-opening.sh, and the pacman
hook invokes it. _CCO_LIB_DIR probes for lib/ beside the script and
falls back to the script's own directory, so the repo tree and the flat
/usr/local/bin deploy layout both work; both branches were exercised.

install_all deploys the libs and uninstall_all removes them in this same
commit, so there is never a deployed copy that is split but missing its
libs -- the failure that had to be repaired for install_leechblock.

The libs deploy flat into /usr/local/bin rather than into
/usr/local/share/digital_wellbeing/lib, because install_leechblock_payload
does an rm -rf mirror of that directory and would delete them.

Verified beyond the usual gates: a line-multiset check reports zero
missing functional lines, and all three heredoc bodies -- the generated
app wrapper, the generated pacman hook and the help text -- are
byte-identical, which verify_shell_split.sh cannot check. status output
matches the still-monolithic deployed copy byte for byte.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-21 15:51:06 +02:00
63454fa544 install_leechblock: first test suite for the split libs
29 assertions across two files, run via lib/tests/run_all.sh.

Coverage measured, not waived: leechblock_fetch.sh 38.57% (27/70),
leechblock_browsers.sh 36.47% (31/85), config and firefox 0%. The uncovered
lines are exactly the bodies that must never run under test — curl/tar/unzip
downloads, pkill + sudo binary patching, npm install, and writes to
/etc/*/policies.json. Closing the gap needs those externals shimmed.

What the tests actually pin down:
- all three get_latest_tag fallbacks (releases API, tags API, Location
  header), plus JSON-null-is-not-a-tag and the all-sources-exhausted
  non-zero return. A silent failure there installs the wrong version.
- the 8+3 browser tables including every hyphenated key, and the
  command -v gating that decides whether a binary is patched at all.

Also fixes a latent set -u abort: wire_up_browsers interpolates VERSION_DIR,
VERSION and TAG, which only resolve_version sets. Calling it on its own
aborted on an unbound VERSION_DIR; they are now read through locals with
defaults, leaving production output unchanged.

Harness note: PATH must be narrowed to the shim dir for "no browser
installed" to be testable on a machine that has five, but every external the
code under test calls has to be seeded there too — a missing mkdir aborted
the suite under set -e with no stderr, which under kcov looked like a
coverage-tool bug rather than a PATH bug.

The hyphenated array subscripts in the test are quoted for the same reason
they are in the lib: shfmt -w rewrites an unquoted [google-chrome-stable]
into a subtraction expression. Reproduced live in this file during the work.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F3PzGc75gBcdxP3PkHXGcV
2026-08-21 15:39:15 +02:00
1dceca6600 install_leechblock: split into lib/ under the 250-line cap
485 -> 114 lines, with the six install phases moved into four sourced
libs: leechblock_fetch.sh, leechblock_config.sh, leechblock_browsers.sh
and leechblock_firefox.sh.

detect_browsers, replace_browser_in_place and wire_up_browsers stay in
one lib on purpose: detect_browsers writes the BROWSERS/FIREFOXES arrays
that wire_up_browsers reads, and splitting them would create a
write-only cross-file global.

Four justified body changes, all recorded in the evidence:

- Quoted the six hyphenated associative-array keys. shfmt 3.13.1 parses
  an unquoted [google-chrome-stable] as arithmetic and rewrites it to
  [google - chrome - stable], which would break browser detection
  entirely. The pre-commit hook runs shfmt -w on staged files and
  re-stages them, so this corruption would have landed in the commit
  unseen -- this is what caused the previous attempt at this split to be
  reverted. Behaviour-preserving: quoted and unquoted are the same key.

- SCRIPT_DIR is assigned in the entry script rather than inside
  inject_default_config. Left in the lib, ${BASH_SOURCE[0]} resolves to
  lib/, so the defaults JSON lookup would miss and default blocking
  rules would silently never be seeded.

- install_firefox_policy takes ff_found as a parameter instead of
  reading it as a cross-file global, fixing a genuine SC2154 without a
  suppression.

- Added require_cmd unzip. It was used bare under set -Eeuo pipefail, so
  a missing unzip would abort after the version dir was populated but
  before the 'current' symlink was made, leaving a half-installed tree.

verify_shell_split.sh reports additions only -- no -/+ pair -- so
get_latest_tag and replace_browser_in_place moved byte-identically.
--help and unrecognized-flag output are byte-identical to the baseline.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-21 15:32:48 +02:00
47478791d8 hosts/install: split into lib/ under the 250-line cap
Some checks are pending
Pre-commit checks / pre-commit (push) Waiting to run
Shell tests / shell-tests (push) Waiting to run
Shell tests / arch-tests (push) Waiting to run
913 lines down to 150. The ten functions were only 202 lines; the other 712
were a single top-level block, now eight phase functions across six libs. As
in the previous split the bodies were wrapped WITHOUT re-indenting, because
five heredocs use quoted terminators that must stay at column 0 (shfmt
indented them afterwards, which is safe).

The ~320-line custom-blocklist heredoc became custom_entries.hosts. It was
321 of the 370 lines of hosts_write.sh and cannot be split, so that lib could
not otherwise fit the cap; a hosts-format data file is also far easier to
audit and diff than a quoted heredoc.

That move broke a guard, which is the important part of this commit:

  extract_custom_entries_from_script sliced the blocklist out of the script
  between "# Custom blocking entries" and the heredoc terminator. With the
  entries in a data file there is no terminator, so it would have extracted
  NOTHING -- and check_custom_entries_protection, the guard whose entire job
  is refusing to drop blocked domains, would have compared against an empty
  list and passed everything. It now reads the data file, and the extracted
  set is identical: 254 domains, diffed.

A second, pre-existing bug surfaced while testing:

  extract_unblock_entries_from_script matched its OWN marker lines.
  PROTECTED_UNBLOCK_LIST_START and _END are word characters, so the
  '^# [a-zA-Z0-9._-]+$' pattern pulled both into the whitelist as if they
  were domains. Both are in this machine's live state file right now.
  Renaming either marker would have read as the whitelist growing and blocked
  the install outright.

There is no dry-run or status mode, so this file cannot be proven by running
it: an aborted run leaves /etc/hosts mutable with the guard's bind mount down.
The proof is textual instead. Every one of the 8 wrapped phase bodies is
byte-identical to its original line range; a line-multiset check found 0 lines
lost; and an indentation-insensitive comparison against the pre-split file
leaves exactly 8 absent lines, each an intentional edit (the rewritten
extractor, three `readlink -f "$0"` made overridable, the /etc/hosts fallback
made overridable, and the heredoc markers). /etc/hosts is byte-unchanged
across the whole session, and `--help` output is identical.

36 assertions. Both anti-tamper guards at 100% line coverage (75/75 and
49/49), covering both directions of each: removing a blocked domain is
refused and adding one allowed; growing the whitelist is refused and shrinking
it allowed. A same-count swap is refused too, which a count-only check would
have let through.

The install phases themselves are deliberately untested -- running them would
take down the protection they install. That is weaker evidence than the two
previous splits had, and it is inherent to this file rather than a gap.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F3PzGc75gBcdxP3PkHXGcV
2026-08-21 14:45:04 +02:00
241652cade generate_study_materials: split into lib/ under the 250-line cap
1017 lines down to 148. Six libs: the per-language doc-URL builders (split in
two only for the cap), the lookup/dispatch layer, and one per generation phase.

The hard part was not the ten functions but the 335-line top-level block,
which extract_shell_functions.py cannot move because it only relocates
functions. It is now generate_doc_links / generate_anki_cards /
generate_llm_prompt, wrapped WITHOUT re-indenting the bodies: five heredocs
use quoted terminators, which must stay at column 0.

Proof the split is behaviour-preserving: a golden fixture captured before any
edit, re-checked after every step. All three outputs -- anki_cards.txt,
documentation_links.md, llm_anki_prompt.md -- stay byte-identical by sha256sum,
and stdout matches too.

Four latent defects surfaced while writing the tests, all fixed:

  python_doc_url required a $2 it never reads, and get_doc_url has always
  called it with one argument. That only worked because the entry script runs
  `set -e` without `-u`, so "$2" silently expanded to empty; any caller with
  `set -u` aborted. Defaulted, along with three more positionals documented as
  optional but declared required.

  The two imports loops hardcoded `head -20` while the other seven honoured
  TOP_N, so --top was silently ignored for imports.

  Only one of nine term loops skipped comment lines, so a '#' line in an
  analysis file became a flashcard whose front was a comment.

  Introduced and fixed within this change: the comment filter has to run
  BEFORE `head -n` or comment lines count against the limit, and it needs
  `|| true`, since grep exits 1 when it filters every line and the callers run
  under `set -e`.

The golden fixture cannot show the last three, because it has no comments and
fewer than 20 imports -- which is exactly why they survived. A second fixture
with 25 imports and comment lines, at --top 3, emitted 20 imports and two
comment cards before, and 3 imports with zero leakage after.

166 assertions across 4 test files. 100% line coverage on study_doc_urls.sh
(38/38), study_doc_urls_more.sh (30/30), study_doc_lookup.sh (49/49) and
study_gen_llm.sh (54/54). study_gen_docs.sh 93.10% and study_gen_anki.sh
96.15%, where every remaining line is a `} >>"$FILE"` closing a brace group
whose output a passing test asserts -- the same kcov attribution artifact
recorded for services_hosts.sh.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F3PzGc75gBcdxP3PkHXGcV
2026-08-21 14:32:08 +02:00
08f3c764b2 check_and_enable_services: a warning must never mask an error
Some checks are pending
Pre-commit checks / pre-commit (push) Waiting to run
Shell tests / shell-tests (push) Waiting to run
Shell tests / arch-tests (push) Waiting to run
Eight checks assigned status="warning" unconditionally, so a check running
later in the same function overwrote the status="error" an earlier one had
set. The five other sites in this family already guard the assignment with
`if [[ $status != "error" ]]`, so this was an inconsistency, not a design.

Only half of it was load-bearing, and the two halves fail differently:

  services_units.sh (4 sites) reports through report_and_fix, which repairs
  ONLY on "error". A downgrade there meant no repair ran at all. The is-active
  check is the LAST status assignment in each of the three functions, so it
  could mask any error above it -- in check_periodic_systems, three separate
  is-enabled checks. A timer that was both disabled and inactive, which is the
  ordinary shape of a broken timer, was reported and then left broken.

  services_hosts.sh (4 sites) gates its own repair on `status != "ok"`, so the
  repair still ran. There the bug printed a real error as a yellow [!] warning
  and understated the summary row.

Demonstrated rather than argued, against a simulated broken machine (timer
disabled AND inactive, unit files present):

  without the guards:  status=warning, repair ran 0 times
  with the guards:     status=error,   repair ran 1 time

The seven assertions that previously pinned the broken behaviour now assert
the correct behaviour; they were rewritten first and failed, then the guards
made them pass. Two further cases cover the masking shape directly: a broken
guard-lib instance must still be repaired when a later check warns.

`--status --dry-run` output is still byte-identical to rev 6691cb26, because
this machine has no disabled timer and no missing pacman hook -- the buggy
path is never taken here, which is precisely why it went unnoticed. The A/B
above is the evidence the live diff cannot provide.

222 assertions across 10 files. services_units.sh holds 100% coverage;
services_hosts.sh rises from 86.05% to 93.02%. test_services_hosts.sh needed
splitting twice to stay under the 250-line cap (hosts_fix, resolved), and
stage_hosts_ok moved into the harness so both new files can use it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F3PzGc75gBcdxP3PkHXGcV
2026-08-21 14:16:01 +02:00
0a69d18390 check_and_enable_services: 100% coverage via a redirectable path prefix
Follow-up to 9085a4e5, which recorded services_units.sh at 84.51% because
its remaining branches sit behind [[ -f /etc/systemd/system/*.service ]] --
absolute root-owned paths that exist on this machine, so the else-branch
never runs and no PATH shim can redirect it.

All 87 such paths across the seven check libs now go through $SYSROOT, read
from SERVICES_ROOT. The prefix is deliberately NOT defaulted. Three commands
in hosts_fix_resolved run outside `run` and so are not stopped by --dry-run
(chattr -i on resolved.conf.d, find -delete over its *.conf, and an append to
resolved.conf); with an empty default, a test that forgot to set SERVICES_ROOT
would really delete this machine's resolved.conf drop-ins. Unset is therefore
a hard error and only an explicitly empty value means the real filesystem.

The harness now owns PATH outright: $FAKE_BIN for shims and staged fake apps,
$REAL_BIN for a curated set of symlinks to genuine coreutils. The machine's
/usr/bin is deliberately absent -- a really-installed discord and chromium
otherwise make the "no target apps present" branches untestable, which is
exactly what happened until these tests caught it.

Coverage, measured against run_all.sh because two test files jointly cover
services_common.sh after the 250-cap split: common 55/55, report 27/27,
units 72/72 (was 84.51%), wrappers 100/100, apps 66/66, browser 79/79 --
seven libs at 100%. services_hosts.sh reads 86.05% and services_hosts_fix.sh
97.30%, and those 14 lines are a kcov attribution artifact rather than a gap.
Falsified rather than assumed: the suite prints "51 passed, 0 failed" when run
under kcov, so no group is skipped, and every supposedly-uncovered line is an
issues+=("...") whose string can only reach stdout through the print loop
below it -- with a passing assertion matching that exact string.

Two falsification checks, both re-run after the rewrite: `--status --dry-run`
as root stays byte-identical to rev 6691cb26 modulo the Date: line, and the
sha256sums of /etc/hosts, /etc/nsswitch.conf and /etc/systemd/resolved.conf
plus the contents of resolved.conf.d are unchanged across a full suite run.
The second one is what makes a missed prefix site detectable instead of a
matter of inspection: the repair paths really do write.

220 assertions across 8 test files. Also fixes a bug in the harness's own
lsattr shim, where printf with a leading-dash format string parsed the dashes
as options and silently produced nothing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F3PzGc75gBcdxP3PkHXGcV
2026-08-21 14:08:11 +02:00
9085a4e508 check_and_enable_services: split into lib/ under the 250-line cap
Some checks are pending
Pre-commit checks / pre-commit (push) Waiting to run
Shell tests / shell-tests (push) Waiting to run
Shell tests / arch-tests (push) Waiting to run
1225 lines down to 202, with the 21 functions moved into eight sourced
libraries grouped by subject. The entry script now holds only the
configuration, the argument parsing and the order the checks run in.

check_hosts was the one function that could not move verbatim: at 260 lines
it would have stayed over the cap on its own, so its repair half is now
hosts_fix_nsswitch / hosts_fix_resolved / hosts_repair_all in
services_hosts_fix.sh. Repair order is preserved (resolver config, then the
hosts file, then the guard-lib migration), and hosts_repair_all returns
non-zero under --dry-run so a dry run can no longer promote a status to "ok"
on the strength of repairs that never happened.

SERVICE_STATUS was a write-only global across five libs, which is a real
SC2034 rather than noise: the pre-commit hook runs shellcheck with no -x and
genuinely cannot see the one reader. It now goes through set_service_status /
get_service_status, which also rejects a duplicate write — each check owns
exactly one summary row, so a second write means two checks are fighting over
it. No suppression was added anywhere.

Proof the behaviour is unchanged: `--status --dry-run` run as root against
this machine is byte-identical to the same command from a worktree at the
pre-split revision, modulo the Date: line. verify_shell_split.sh reports
check_hosts as the single expected difference; the other 20 functions hash
identically. 72 assertions across three test files pass, with 100% line
coverage on services_common.sh (54/54) and services_report.sh (27/27).

services_units.sh measures 84.51% (60/71). The 11 uncovered lines are the
else-branches of [[ -f /etc/systemd/system/*.service ]] and
[[ -f /usr/local/bin/*.sh ]] probes — absolute root-owned paths that exist on
this machine and that no PATH shim can redirect. Reaching them needs a
root-prefix rewrite of live enforcement logic, which is not a line-count fix
and would forfeit the verbatim-move proof for everything else.

Found while testing, deliberately NOT fixed here: in all three unit checks
the is-active branch assigns status="warning" unconditionally, overwriting
the status="error" the is-enabled branch set just above it. A timer that is
both disabled and inactive therefore never gets repaired, because
report_and_fix only repairs on "error". It is identical in the pre-split
file, so this split neither caused nor hides it. The tests pin the current
behaviour and name it; fixing it changes enforcement and earns its own commit.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F3PzGc75gBcdxP3PkHXGcV
2026-08-21 13:47:05 +02:00
7d7b3c71f9 focus_owner: split EnforcementRunner.kt under the 250-line cap
Extracts four cohesive concerns into standalone files:
LocationAcquisition (fix acquisition), HomeLocationStore (home
read/write), PolicyPinning (VPN/DNS pin + visibility sweep),
SweepablePackages (installed-package enumeration). Public API
unchanged -- EnforcementRunner.best(...) deliberately stays put since
EnforcementRunnerBestTest.kt calls it directly; every other public
method is a one-line delegating wrapper.

Verified with gradlew compileDebugKotlin + test --rerun-tasks
(BUILD SUCCESSFUL, 40 tests / 0 failures, including the 6
EnforcementRunnerBestTest cases unmodified).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
2026-08-21 13:17:59 +02:00
6efb4baf04 focus_owner: split DevicePolicyBridge.kt under the 250-line cap
Some checks failed
Pre-commit checks / pre-commit (push) Has been cancelled
Shell tests / shell-tests (push) Has been cancelled
Shell tests / arch-tests (push) Has been cancelled
Kotlin has no part-of like Dart, so this extracts three cohesive
method groups into standalone internal helper classes held as
private delegates: DevicePolicyVpnDns (VPN/Private DNS pinning),
DevicePolicyLocation (location permissions), DevicePolicyUninstallGuard
(uninstall blocking). DevicePolicyBridge's public API is unchanged --
every method MainActivity.kt/EnforcementService.kt call directly
still exists as a one-line delegating wrapper.

Verified with gradlew compileDebugKotlin + test --rerun-tasks
(BUILD SUCCESSFUL, 40 tests / 0 failures).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
2026-08-18 23:56:21 +02:00
327d48badd Archive legacy hosts-guard subsystem: superseded by guard-lib
setup_hosts_guard.sh was NEXT_SESSION_PROMPT.md's next split target, but
live-machine inspection showed it (and 16 supporting files - enforce-*.sh,
6 systemd units, install_pacman_hooks.sh, pacman-hooks/, psychological/,
both READMEs) is fully retired in favor of guard-lib, migrated 2026-07-04.
User confirmed archive over split, then confirmed the larger 17-file scope
once the directory was surveyed. Full history preserved via git filter-repo,
live at github.com/kuhyx/testsAndMisc-archive/tree/main/linux_configuration/scripts/periodic_background/hosts/guard.
plugins/{nsswitch,resolved}-plugin.sh stay - they're guard-lib's live
plugins, referenced by absolute path from /etc/guard-lib/targets/*.conf.

check_and_enable_services.sh's check_hosts() rewritten to check guard-lib
state (guardctl file-guard status) instead of the retired units/scripts it
used to check and self-repair - verified live, now reports the hosts
service ok against the actual migrated machine instead of a false error.
copilot-instructions.md and one install.sh comment updated to match.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
2026-08-18 22:49:54 +02:00
015eb48714 diagnose_pacman_hook_stall.sh: split 493 -> 249 + 6 libs, 100% coverage
Some checks are pending
Pre-commit checks / pre-commit (push) Waiting to run
Shell tests / shell-tests (push) Waiting to run
Shell tests / arch-tests (push) Waiting to run
Confirmed live (not dead code) via the dead-code check-in before any code
was touched -- the last two candidates this pattern was tried on were both
archived instead. Kept run_one/cleanup/log_size/main together in the entry
script: a prior session (2527fb3) already proved splitting run_one out
produces a real SC2153/SC2034 pair since PACMAN_PID/LAST_ELAPSED cross the
EXIT trap, not a suppressible false positive.

Fixed a real bug found while writing tests: print_summary's inline awk had
a dead `NR == 0` guard (printf on an empty array still emits one blank
line), reachable via `-n 0`. Refactored mem_available_mb, descendant_pids/
kill_tree, capture_stall's two output blocks, and print_summary's median
calc after confirming empirically (bash -x tracing) that kcov's
xtrace-based coverage can never see a `done <redirect>`, `} >redirect`, or
inline-awk line -- user chose to fix the code over the coverage tool.
descendant_pids specifically needed an explicit-fd process substitution
(not a pipe) to keep its array mutation in the current shell.

Made PACMAN_BIN/PACMAN_LOG/CACHE_DIR/PACMAN_LOCK/LOAD_FLOOR_MB/
LOAD_MIN_FREE_MB env-overridable for testing. Caught and fixed, before it
ran unbounded, a --with-load test that would have dd'd 13-20+ GB into
/dev/shm using this box's real available memory.

56 lib-level + 13 entry-level PATH-shim/subprocess assertions, 0 failed.
The entry test itself grew past 250 lines while adding coverage and had to
be split into a shared harness plus two test files, mid-session.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
2026-08-18 22:33:57 +02:00
071d706d8a Archive install_plagiarism_tools.sh: unused, moved to testsAndMisc-archive
Full history preserved via git filter-repo, including the pre-rename path
from before the single_use/periodic_background split. Live at
github.com/kuhyx/testsAndMisc-archive/tree/main/linux_configuration/scripts/single_use/utils.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
2026-08-18 20:50:05 +02:00
2a7b9d90ac install_plagiarism_tools.sh: split 534 -> 94 + 3 libs, with a real test harness
Wraps the four install stages into named functions, one per lib/plagiarism_*.sh
sibling (matching install_exercism.sh's convention), and extracts the two
embedded Python heredocs into real, lint-clean check_plagiarism.py/sherlock.py
files -- check_plagiarism.py needed a second split into plagiarism_similarity.py
to also clear the 250-line cap. Every function reaches 100% shell coverage via
a PATH-shim test harness (fake python3/pip/git/java/wine); the two Python CLIs
were separately verified against a real venv with actual nltk/scikit-learn.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
2026-08-18 20:41:18 +02:00
b11b93457f deploy: split 875 -> 241 + 6, finishing phone_focus_mode
Some checks are pending
Pre-commit checks / pre-commit (push) Waiting to run
Shell tests / shell-tests (push) Waiting to run
Shell tests / arch-tests (push) Waiting to run
Held until last on purpose: all five enforcer splits this session grew
deploy.sh's two hardcoded file lists, so cutting it earlier would have left
every "add to BOTH lists" step pointing at a stale location. Those lists are
final as of the focus_ctl commit, so this is now safe.

do_deploy was 421 lines -- over the cap on its own, so moving it whole would
have achieved nothing. It is cut at the phase boundaries it already prints
("[4/7] Uploading scripts...", "[5/7] Setting permissions...") plus two more
inside the push phase, giving seven named functions instead of one wall.

The two hardcoded lists now live in SEPARATE files, which is the real risk
this commit introduces. deploy_phases.sh holds the push list and
deploy_install.sh the cp list; each header names the other and says what
happens when they drift -- the file is staged, never lands, and whatever
sources it fails to start with no obvious cause. Checked in both directions
after the move: the only asymmetry is config_secrets.sh, which a conditional
block handles so the phone's real coordinates survive a deploy. That was
asymmetric at HEAD too.

check_coords now RETURNS whether the coordinates are placeholders instead of
assigning $NEEDS_GPS_FETCH. The flag is written in deploy.sh and read in
deploy_phases.sh, so assigning it from deploy_gps.sh as well left that file
writing a global it never reads. Behaviour proven identical to HEAD: real
coords -> 0, placeholders -> 1.

NO TESTS, and that is a deliberate limit rather than an omission. Every
phase here is a sequence of adb calls against a real device; a stub layer
deep enough to mean anything would be modelling the phone, not testing the
script. Verification is that deploy.sh parses, sources all six libraries and
reaches its usage message -- which catches a broken split, not a wrong one.

515 assertions across ten phone suites still pass; run_phone.sh --help
exits 0.

Over-cap 16 -> 15, and NO phone_focus_mode file is over the cap any more.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
2026-08-17 17:53:52 +02:00
3ddc13f671 focus_ctl: split 1091 -> 171 + 8 siblings, one per enforcer
The largest file in phone_focus_mode, and the last one blocking deploy.sh:
splitting it grows deploy.sh's two hardcoded lists, so those lists are only
final now. All 55 functions moved verbatim.

Three while-read loops were audited before extraction. Two call pm in their
bodies and moved untouched. The third, iter_whitelist_packages, is fed by a
pipe from printf and reads nothing from stdin -- already safe, and now said
so in ctl_usage.sh's header so the distinction is not lost by a later editor
who has only read the warning in daemon_apps.sh.

136 assertions and 22 mutations, all killed. Three survived the first run
and every one was a defect in MY OWN tests, not in the subject:

  - cmd_status carries a THIRD copy of the Haversine, and I had asserted
    only that it printed "some number". A wrong Earth radius sailed through.
    Now the value is checked against a seeded fix ~4.3 km from home.
  - the two whitelist filters were each tested with input the OTHER filter
    also rejects, so either could be deleted with nothing failing. Fixed
    with a punctuation-free undotted token (only the dot check rejects it)
    and a dotted token carrying an illegal character (only the charset check
    does), making each independently observable.

That last investigation turned up dead code: the `#*)` case arm is provably
equivalent, because `set -- $line` makes a comment's first token "#", which
the dot check already rejects. Removed rather than covered by a test that
cannot fail.

Coverage is NOT 100% and the real numbers are in the evidence: 41.79% to
92.98% across the eight, up from 5-22%. Unlike the daemon split, most of
what remains is genuinely untested -- the per-enforcer status blocks that
print mount state, APK hashes and offload settings, each needing its own
device fixture. The mutations prove the assertions that exist are load-
bearing; they do not prove those branches work.

jscpd checked in a clean worktree at 1.57%: eight files each carrying a
near-identical start/stop/status trio is exactly what that gate is for, so
the tests drive the trios through tables instead of repeating them.

Two drift hazards recorded, not fixed: ctl_is_curfew_now duplicates
daemon_location.sh's is_curfew_now (pinned by an agreement test across every
boundary minute), and cmd_status's awk is the third Haversine.

Over-cap count 17 -> 16. deploy.sh is the last one here.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
2026-08-17 17:46:04 +02:00
7dc3c94160 config.sh: split 571 -> 250, with a fingerprint that actually works
Some checks are pending
Pre-commit checks / pre-commit (push) Waiting to run
Shell tests / shell-tests (push) Waiting to run
Shell tests / arch-tests (push) Waiting to run
The brief's prescribed safety net for this file DOES NOT WORK, and that is
the main finding here. It specifies asdict + json.dumps(default=str) +
sha256 against baseline 1624750c. Four policy fields are frozenset, which
json cannot encode, so default=str stringifies them in SET ITERATION ORDER
-- random per process. Three consecutive runs gave three different digests;
pinning PYTHONHASHSEED=0 gave one. The recipe can never match a recorded
baseline, so it would have flagged every split as a regression regardless
of correctness.

The repo already ships a deterministic serializer. Before trusting it I
checked it was not a weaker proxy: policy_to_json emits all 16 dataclass
fields plus 6 more, so it is strictly stronger than asdict. Baseline
83e05e82, unchanged through all six extractions.

Two audits ran BEFORE any extraction:

  - the loader regex-scans config.sh's TEXT ALONE, so the fifteen names it
    reads are pinned here. Moving one does not error -- it silently yields
    an empty set, and for WHITELIST that reads as "hide everything".
  - extractions are variable-scoped, never line-range. A line-range attempt
    cut through the WHITELIST string, leaving an unterminated quote; 15
    variables vanished from the environment WHILE THE FINGERPRINT STILL
    MATCHED, because the loader's regex tolerated the mangled file. That is
    why the environment diff exists alongside the hash.

The environment check earned its keep twice: it also caught STATE_DIR being
left behind the source line, which expanded every path against an empty
prefix (/status.json rather than the real path) with the policy hash green
throughout. 79 variables, byte-identical either side, and every parsed list
compared entry-by-entry: 29/16/46/1/1/2/15, all matching.

Rationale prose moves to docs/policy-lists.md with pointers left behind,
including the blocks inside the list strings -- the loader and the shell
both ignore '#' lines there.

All five siblings are in both deploy lists. deploy.sh still sources the
chain from the checkout, run_phone.sh --help exits 0, 379 shell assertions
pass across nine suites, and focus_policy's 137 pytest cases pass.

config.sh is at EXACTLY 250 with no headroom; 184 of those lines are
loader-pinned lists that cannot move. Over-cap count 18 -> 17.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
2026-08-17 17:17:02 +02:00
094c4765f3 focus_daemon: split 543 -> 206 + 165 + 140 + 94, with 110 tests
Some checks are pending
Pre-commit checks / pre-commit (push) Waiting to run
Shell tests / shell-tests (push) Waiting to run
Shell tests / arch-tests (push) Waiting to run
Location/curfew goes to daemon_location.sh, the derived state files to
daemon_state.sh, the app layer to daemon_apps.sh. All 23 functions moved
verbatim -- nothing restructured, which matters more here than anywhere
else in this session.

focus_daemon.sh has five `while IFS= read -r pkg; do ... done <file` loops
and EVERY body calls pm. Piping any of them in lets pm inherit and drain
the loop's stdin, so it would process one package instead of all of them --
measured elsewhere in this repo as 1 of 54. That audit ran BEFORE the
extraction, not after coverage came back short, and all five moved
untouched with a note above them saying why.

CURRENT_MODE and _CURFEW_TICK_CACHED are each written in both the entry
script and a library, so both stay declared with main, same call as
NET_BUILT in the curfew split.

110 assertions and 33 mutations, all killed. Two survived the first run and
both were defects in my own tests: the whitelist and sysprotect exactness
checks used a SUPERSTRING of a listed entry, which fails under grep -qF and
grep -qxF alike and so proved nothing about which was in use.

Two subjects are NOT at 100%, and the numbers are reported as measured:

  daemon_state.sh     67/67 = 100.00%
  daemon_location.sh  26/40 =  65.00%
  daemon_apps.sh      71/76 =  93.42%

Both shortfalls are kcov attribution failures, shown to be artifacts rather
than gaps. daemon_location.sh's missing lines are all inside the embedded
awk Haversine; mutating the Earth radius and dropping the longitude term
both fail the suite, and an isolated probe shows a one-line awk measuring
100% where the identical multi-line one measures 0%. Collapsing the formula
to satisfy the tool would cost the readability of the only real mathematics
in the repo, so it stays.

daemon_apps.sh's five are the trailing `done <file` of the very loops
described above. That is the same artifact piping fixed in curfew_net.sh,
and it is exactly what must NOT be done here. Redirecting two of them from
/dev/null fails the suite, so the lines run; all three are pinned in the
mutation spec instead.

Over-cap count 19 -> 18.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
2026-08-17 16:54:04 +02:00
0a07684cca hosts_enforcer: split 421 -> 207 + 166 + 91, both libs at 100%
Some checks are pending
Pre-commit checks / pre-commit (push) Waiting to run
Shell tests / shell-tests (push) Waiting to run
Shell tests / arch-tests (push) Waiting to run
The bind-mount logic goes to hosts_mount.sh, the Magisk-module and
cache-invalidation logic to hosts_magisk.sh. Both flat siblings, both in
both deploy lists; the two lists were then checked to correspond exactly
in both directions, since a push with no matching cp stages a file that
never lands and the enforcer would fail to source it.

Audited for stdin-draining `while read` loops BEFORE extracting, because
piping one would change behaviour in a way stubs cannot catch. There are
none here. Also confirmed no function writes a global, so there was no
cross-half hazard of the kind that kept NET_BUILT in curfew_enforcer.sh.

69 assertions, 43/43 and 62/62 lines, 17 mutations all killed. Two survived
the first run and both were real findings:

  - the unmount give-up threshold was not pinned. Raising it from 5 to 500
    changed nothing observable, so the enforcer could have spun 500 times
    per poll against a mount that was never going to clear. Now asserted as
    a call count.
  - the `[ ! -f $HOSTS_TARGET ] && return 1` guard in
    is_bind_mounted_correctly is PROVABLY EQUIVALENT: sha256_of returns
    empty for a missing file, so the existing -n check already rejects that
    case. No test can distinguish it. Equivalent code is a claim of a check
    that is not being made, so it is deleted rather than covered by a test
    that would pass either way. Old and new bodies were compared across all
    four input states -- both-missing, target-missing, match, mismatch --
    and agree on every one.

That deletion is the only non-verbatim function; the other 19 moved
untouched, making this the first fully verbatim extraction of the session.

Ordering is asserted where it is load-bearing: /system must go back to ro
after rw, and +i must be restored after being dropped. Presence assertions
alone would pass with either pair reversed.

UNVERIFIED ON DEVICE -- the highest-stakes of the four enforcer splits now
outstanding, since a failure to source leaves nothing blocking while the
phone looks normal. Over-cap count 20 -> 19.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
2026-08-17 16:30:56 +02:00
9f2e208257 curfew_enforcer: split 367 -> 244 + 156, net layer at 100%
The nine network-curfew functions -- the per-UID iptables allow-list that
cuts every app outside $NIGHT_WHITELIST off the network overnight -- move
into curfew_net.sh. NET_BUILT stays in the enforcer: both files write it,
so it is a genuine shared global rather than a seam violation.

Placed as a FLAT sibling, not under lib/. deploy.sh pushes phone scripts
into one directory, so a lib/ path would simply not exist on the device;
dns_iptables.sh and tether_iptables.sh sit beside their enforcers for the
same reason. Added to BOTH deploy lists, and deliberately not to the chmod
list -- sourced files need no exec bit there, matching the other two.

41 assertions, 60/60 lines, 17 mutations all killed. The one that matters
most flips the 10000-19999 REJECT to ACCEPT, which would silently disable
the entire cut-off while leaving a chain that looks populated. The suite
also asserts rule ORDER, not just presence: every ACCEPT must precede that
REJECT, or the whitelist blocks the very apps it exists to permit.

night_uids and fill_net_chain are reported as non-verbatim by
verify_shell_split.sh, correctly: both ended in `done < file`, a line kcov
instruments and bash never reports, which alone held the file at 96.77%.
Both are now piped. Neither loop reads stdin internally, so the subshell is
immaterial -- unlike pull_apks in backup_capture.sh, where the same change
would reintroduce a known bug. Behaviour was checked rather than assumed:
night_uids emits the same uids, and fill_net_chain emits an identical
11-rule sequence, diffed against the pre-split bodies.

UNVERIFIED ON DEVICE -- joins tether_enforcer.sh and dns_enforcer.sh.
Over-cap count 21 -> 20.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
2026-08-17 16:18:05 +02:00
61be666329 phone_backup: split 333 -> 229 + 114, with the library at 100%
Some checks are pending
Pre-commit checks / pre-commit (push) Waiting to run
Shell tests / shell-tests (push) Waiting to run
Shell tests / arch-tests (push) Waiting to run
The capture half -- dump_packages, pull_apks, pull_sdcard, dump_settings
and dump_focus_state -- moves into lib/backup_capture.sh. SDCARD_DIRS goes
with pull_sdcard, its only reader: left behind it would be a global the
entry script never reads (SC2034), and the hook runs shellcheck without -x
so each file has to stand alone.

This code decides what a phone backup actually contains and had no test at
all. It has one now, against a fake device mocking adb_cmd -- the single
boundary all five functions cross. 22 assertions, 52/52 lines, and 13
mutations all killed. One of those mutations reintroduces the stdin-
draining bug pull_apks' own comment documents (it pulled 1 of 54 packages
before it was fixed); the suite catches it.

Nine of the ten functions moved verbatim per verify_shell_split.sh.
dump_focus_state is the exception and the tool correctly says so: its
`{ ... } >file` group hid a line kcov instruments but bash never reports,
and rewriting it as individual redirects drew SC2129 instead. One printf
satisfies both. Its rendered focus-state.md was diffed against HEAD's
output on the same inputs -- byte-identical.

Same for three multi-line literals (SDCARD_DIRS and two mapfile process
substitutions), which were stuck at 1/N covered for the same reason.

phone_backup.sh is PC-side and in neither deploy list, so no list edits.
--help exits 0; --verify on an empty dir exits 1 naming the four missing
files, matching HEAD exactly. Over-cap count 22 -> 21.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
2026-08-17 16:02:25 +02:00
0f210e5532 monitor: backfill to 100% coverage, fixing two defects it exposed
Some checks are pending
Pre-commit checks / pre-commit (push) Waiting to run
Shell tests / shell-tests (push) Waiting to run
Shell tests / arch-tests (push) Waiting to run
The split shipped verbatim-proven and green, but measuring it afterwards
showed the tests barely touched it: monitor.sh 44.44%, the policy probes
7.69%, and the health probes never loaded at all. Every _check_* decides
whether phone enforcement is reported as healthy, so an untested probe is
an enforcement failure that reports itself as fine.

The mock stubs adb_root_shell at the function boundary rather than putting
a fake adb on PATH: the probes never invoke adb, they call a function from
adb_common.sh, so a PATH stub would never be consulted. Dispatch is by
pattern, not exact string, because the real commands embed awk programs
with escaped $2, nested quotes and 2>/dev/null redirections.

Two defects the new tests found:

  - _mon_escape_json never escaped double quotes. Written as
    ${escaped//"/\"}, bash reads the bare quote as opening a quoted string,
    so the pattern silently degraded to `/\` with an empty replacement. Any
    check message containing a quote produced invalid JSON.
  - monitor_print_format_warning's multi-line array literal and
    collect_snapshot's `{ ... } >file` group held 12 lines that no test
    could ever cover: kcov instruments the continuation lines and the
    closing brace, but bash only reports a statement at its first line.
    Reproduced in an isolated 9-line case before changing anything.

Assertions are mutation-proofed: 18 mutations of the probes' status
decisions, 18 killed. The first run had one survivor -- the repairable
flag was asserted nowhere -- now pinned in both directions.

  monitor.sh                104/104 = 100.00%  (was 44.44%)
  monitor_checks_policy.sh    65/65 = 100.00%  (was  7.69%)
  monitor_checks_health.sh    79/79 = 100.00%  (was     0%)
  test_monitor.sh          82 passed, 0 failed (was 9)

Cases live in two sourced files purely to stay under the 250-line cap;
test_monitor.sh remains the single entry point, so one coverage command
still measures all three subjects. Over-cap count unchanged at 22.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
2026-08-17 15:54:04 +02:00
a174b2a3c4 meta: measurable shell coverage, and dns_iptables.sh at 100%
kcov works here once invoked correctly, so shell coverage is measurable
after all. shell_coverage.sh wraps it as a gate; the XML parsing lives in
shell_coverage_report.py per the rule against inlining another language's
logic in a shell script.

Two kcov traps are documented in the wrapper because both fail silently.
It must be handed the test script DIRECTLY: given `bash script.sh` it
instruments the bash binary, finds no shell source, and reports 0/0 at
0.00% -- indistinguishable from "nothing is covered". And per-line detail
exists only in cov.xml; coverage.json carries the percentage alone, so it
cannot say which lines are missing.

First measurement put dns_iptables.sh at 68/74 = 91.89%, with all six
uncovered lines in error and edge branches: chain creation failing, jump
insertion failing, the stale-jump de-dupe log, and the v6 trusted-resolver
ACCEPT. The stub grew failure injection and jump seeding to reach them,
and four new cases take it to 74/74 = 100.00%.

The gate fails closed: deleting the three ensure_chain failure cases drops
it to 93.24%, names lines 23, 24, 37, 40, 41, and exits 1.

Not wired into pre-commit -- kcov is absent on CI, and most shell files
here have no test at all.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
2026-08-17 15:29:08 +02:00
4000c7a2ca dns_enforcer: split 325 -> 171 + 170, and give it a working test
The iptables chain machinery (ensure_chain, trusted_dot_ips,
fill_chain_v4/v6, expected_rule_count, chain_intact, enforce_iptables)
moves into dns_iptables.sh — a sibling, not a lib/ member, because lib/
does not exist on the phone and deploy.sh copies these files flat into
$REMOTE_DIR. Both deploy lists grew the entry, matching how
tether_iptables.sh is carried; it is correctly absent from the chmod 755
line, since a sourced library needs read, not execute.

extract_shell_functions.py refuses to anchor a source line in a
#!/system/bin/sh script, as documented — but when it refuses it still
writes the library and does NOT remove the functions from the source, so
a naive run leaves every function defined twice and the entry script
longer than it started. The excision was done separately; the hash tool
confirms all 14 functions moved verbatim.

test_dns_enforcer.sh is deleted rather than repaired. Its six assertions
targeted extract_ping_ip, extract_package_uid and append_unique_line —
none of which dns_enforcer.sh has ever defined. It died at its source
line and had never run a single assertion since it was added.

Its replacement, test_dns_iptables.sh, covers all seven moved functions
with 20 assertions, staged the way test_tether_enforcer.sh stages its
subject. Three independent mutations were each caught: undercounting the
trusted-resolver ACCEPT, miscounting rules per DoH IP, and moving the
ACCEPT after the blanket REJECT. Shared staging lives in
dns_iptables_harness.sh so both test files stay under the cap.

Not verified on the phone — no deploy has run, by instruction.
Over-cap count 23 -> 22.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
2026-08-17 15:10:10 +02:00
728301c9a4 monitor: split 449 -> 215 + 138 + 136, partitioned by probe kind
lib/monitor.sh was 199 lines over the cap. Its eleven _check_* probes
move into two siblings: monitor_checks_health.sh (format indicators,
battery, storage, the three enforcer daemons) and
monitor_checks_policy.sh (hosts blocklist, DNS chain, launcher,
companion app, boot persistence).

A single monitor_checks.sh was tried first and left at 265 lines — still
over, the net-zero outcome. Two libraries split the cluster at its own
seam and land both comfortably under.

The _MONITOR_* constants are partitioned by reader so no file assigns a
global it never reads. The five read only by the policy probes move into
that file; the ones monitor.sh reads itself or shares between both
children stay put. shellcheck is clean on each file standing alone.

Not a deployment concern: lib/monitor.sh is PC-side, consumed by
run_phone.sh, and deploy.sh never references lib/ — so neither copy list
changes.

Verified three ways: test_monitor.sh reports 9 passed / 0 failed, the
same count as before; verify_shell_split.sh confirms all 25 top-level
functions moved verbatim; and run_phone.sh --help exits 0, proving the
source chain resolves in the real consumer. Over-cap count 24 -> 23.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
2026-08-17 14:59:41 +02:00
2cd62587d6 tether_enforcer: split 267 -> 182 + 103, and grow both deploy lists
First phone_focus_mode split, so it sets the pattern. The nine iptables and
tethering-offload functions move to a SIBLING file, not a lib/ member:
deploy.sh copies a hardcoded per-file list into REMOTE_DIR and never deploys
lib/, so anything sourced on the phone has to sit beside the entry script.

deploy.sh has two hardcoded lists, not one - an adb push into the staging
dir and an adb_root cp out of it. Both were updated; missing the push would
have left the copy pointing at a file that was never staged.

The test harness copies only the entry script into its scratch dir, so it
was taught to copy the sibling too. That is the same coupling as the phone
deployment, in a form that can actually be run - and running it is the
evidence: 17 passed, 0 failed, identical to the pre-split baseline, with the
daemon sourcing the new sibling exactly as it will on the device.

This script is #!/system/bin/sh with no `set -e`, so the extractor refused to
guess an anchor rather than placing the source line somewhere wrong. It went
after the existing config.sh source by hand.

All 20 functions verbatim. Not verified on the phone - the next deploy is
where that gets confirmed.

Over-cap files 25 -> 24.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
2026-08-17 14:38:53 +02:00
2c95425a60 steam_compatibility: split 663 -> 140 + 199 + 156 + 124 + 103
Grouped by the state each function touches rather than by size:

  steam_requirements.sh  199  requirement parsing, scoring, and the hardware
                              detection that feeds it
  steam_report.sh        156  the run loop and the cache it reads
  steam_api.sh           124  library discovery, Steam and ProtonDB calls
  steam_credentials.sh   103  credentials, usage text, presentation helpers

The grouping was forced by the SC2034 writer/reader rule, not chosen. Two
arrangements were discarded: detect_system alone in a lib writes the three
SYSTEM_* globals without reading them, and load_cache_map alone writes
CACHE_MAP without reading it. Both were fixed by putting the writer next to
its reader - detect_system beside score_game, load_cache_map beside main.

Verified: all 33 functions verbatim; every lib passes shellcheck standalone
with no suppression; and the script actually runs - `--help` prints the usage
block and exits 0, which is what proves the four source lines resolve and
main still dispatches.

Over-cap files 26 -> 25.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
2026-08-17 14:32:41 +02:00
76e73830f6 shell: split clean_audio, enforce_vbox_hosts and migrate_hosts_guard
clean_audio.sh          419 -> 230 + 204
  enforce_vbox_hosts.sh   443 -> 220 + 236
  migrate_hosts_guard.sh  444 -> 167 + 162 + 141

migrate_hosts_guard needed two libraries: with all ten work functions moved
the single lib was 295 lines, so migrate and rollback were separated.

The extractor's brace matcher required index > start and so silently skipped
one-line `warn() { ...; }` definitions. Fixed, and regression-checked against
the committed fresh-install/main.sh split byte-for-byte.

The harness earned its keep on a fourth file: adding `export` to libre_translate
to satisfy SC2034 changed two function bodies, and verify_shell_split.sh caught
what lint had accepted. Those exports were reverted, and libre_translate.sh is
parked - what remains after its clean seams both assigns and reads the same
globals, so getting it under the cap needs parse_args restructured rather than
relocated, which is a refactor rather than a verbatim move.

That cost is now written down: a lib must contain the readers of every global
it assigns. The precedent at install_pacman_wrapper.sh:29 is about
definitions-only libs, not about assignments as such, and the pre-commit hook
runs shellcheck with no -x, so a source= directive will not make it follow the
link. This is the shell analogue of the monkeypatch-target trap.

Verified: all 8, 13 and 19 functions verbatim; all four libraries source with
no side effects; shellcheck passes standalone on every file with no
suppression; bash -n passes on all seven. None of the three was executed -
they drive ffmpeg over real media, mutate VirtualBox guest disks, and rewrite
systemd units.

Over-cap files 29 -> 26.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
2026-08-17 14:27:57 +02:00
efd7f022fd fixes/utils: split nvidia_troubleshoot 336 -> 169+181, passwordless 374 -> 195+193
Two single-use scripts with no deployed copy, so the entry+lib shape is
safe. Each keeps the ordering, the safety checks and the user-facing output;
the configuration writers move to a library beside it.

Both scripts write `set -e # Exit on any error`, so extract_shell_functions.py
now matches the directive rather than the whole line. Regression-checked by
re-deriving the committed fresh-install/main.sh split byte-for-byte.

Verified: all 6 and all 9 top-level functions verbatim via the shfmt
harness; both libraries source with no side effects and define every
function moved into them; bash -n passes on all four.

Neither script was run. nvidia_troubleshoot.sh writes /etc/X11 config and
installs pyroveil; setup_passwordless_system.sh writes sudoers and lightdm
autologin, which is what this machine's login depends on.

Over-cap files 31 -> 29.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
2026-08-17 14:18:06 +02:00
dbf2f29b00 _transcribe_diarize: split 356 -> 164 + 143 + 105
The last Python file over the cap:

  _transcribe_media.py    143  _try_import, both duration probes,
                               get_media_duration, ffmpeg transcode, cleanup
  _transcribe_cluster.py  105  _resample_linear, _kmeans_cosine and its
                               iteration loop
  _transcribe_diarize.py  164  the pipeline: audio loading, the classifier,
                               embedding extraction, diarize_segments

The graph is a tree: _transcribe_media imports nothing local, _cluster takes
only _try_import from it, _diarize imports from both. All 13 top-level
definitions verified AST-identical to HEAD, and an AST scan confirms the
three module constants travelled to the modules that use them.

transcribe_fw.py now imports get_media_duration from _transcribe_media
rather than through _transcribe_diarize. Ruff removed that re-export as
unused and broke the import; running the program is what caught it, and
naming the owning module is the correct fix under the no-barrels rule.

Verified: transcribe_fw.py /nonexistent.wav still exits 2 and --help exits
0; 63 tests pass at 100% line and branch on all three modules; 338 pass in
the directory. Over-cap files 33 -> 32, and no Python file is over the cap
any more - the remaining 32 are 29 shell, 2 kotlin, 1 dart.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
2026-08-17 13:59:39 +02:00
fb51e991fe _transcribe_diarize: cover 0% -> 100% before splitting
The largest untested Python file left in the repo, at 356 lines against the
cap. Tests land first so the split that follows is verified against tests
that predate the seam.

63 tests: duration probing through both ffmpeg paths, the transcode fallback
and temp-file cleanup, audio loading's three outcomes, linear resampling,
cosine k-means, the ECAPA classifier loader, per-segment embedding
extraction, and diarize_segments end to end.

numpy is real here, not a double. It is a declared dependency, and the core
of this file is clustering maths - assertions against a mocked numpy would
prove nothing about whether two speakers actually get two labels. The
_try_import seam passes numpy through while stubbing torch and speechbrain,
so the tests still run in CI with neither installed. The clustering tests
assert real properties: two opposed groups separate, magnitude is ignored
because the metric is cosine, and a fixed seed reproduces.

The last three coverage gaps were _load_audio's ffmpeg fallback in both
outcomes, the minimum-slice widening, and the zero-iteration k-means branch.

Split across six test modules after two intermediate arrangements went over
the cap, and one move silently broke a test by separating it from its
doubles - caught by the test count holding at 63 through every rearrangement.
338 tests pass in the directory, up from 275 by exactly the 63 added.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
2026-08-17 13:54:04 +02:00
bda24b6305 transcribe_helpers: split 289 -> 177 + 142
Some checks are pending
Pre-commit checks / pre-commit (push) Waiting to run
Python tests / test (push) Waiting to run
Shell tests / shell-tests (push) Waiting to run
Shell tests / arch-tests (push) Waiting to run
Split along the natural seam now that the file is fully covered:

  _transcribe_probes.py  177  _try_import, the four dependency probes,
                              generate_sine_wav, prepare_model, test_cuda
  transcribe_helpers.py  142  the eight _handle_* functions and main()

All 18 top-level definitions verified AST-identical to HEAD, and an AST
scan confirms neither module references a name it does not bind.

transcribe.sh is untouched - it invokes tools/transcribe_helpers.py by
path, and that path still exists with the same name and executable bit.
The CLI tests' patch targets did not move either, because main() dispatches
to names imported into its own namespace.

Verified by running every subcommand and reading the exit status directly,
since those codes are the contract transcribe.sh branches on:
python-version 0, check-faster-whisper 7, check-ctranslate2 1,
check-diarization 0, deps-installed 0; generate-wav writes the same
96044-byte WAV as before the split. 41 tests pass at 100% line and branch
on both modules; 243 pass in the directory. Over-cap files 35 -> 34.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
2026-08-17 13:33:38 +02:00
7587cd9604 transcribe_helpers: cover 0% -> 100% before splitting
transcribe_helpers.py is 289 lines against the 250-line cap and was
completely untested. Tests land first, in their own commit, so the split
that follows is verified against tests that predate the seam.

Left at its current path rather than moved under python_pkg/: transcribe.sh
invokes it by path inside a dedicated ML venv, and transcribe_fw.py imports
two siblings by bare name, so the closure is five files (1236 lines), not
three. Tests go in linux_configuration/tests/ instead, whose conftest
already puts standalone script directories on sys.path - the same
arrangement the usage_report work used, and still collected by CI.

The ML dependencies are never installed in CI and the module reaches them
only through _try_import, so patching that one seam covers every probe in
both directions. An AST scan at every nesting level confirmed there is no
other path to them: the only direct numpy imports sit under TYPE_CHECKING.

41 tests, 100% line and branch. The last gap was the dispatch guard at
line 284, unreachable through the CLI because argparse's choices list and
the dispatch dict carry the same eight commands; covering it by admitting a
ninth choice also documents why the guard is there.

Split across four test files because a single module hit 291 lines - over
the cap this work exists to clear.

Verified: python-version prints 3.14, check-faster-whisper exits 7 with no
ML deps present, generate-wav writes a 96044-byte WAV; 243 tests pass in the
directory, up from 202 by exactly the 41 added.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
2026-08-17 13:29:07 +02:00
377a352d48 vscode_optimizer: split _optimize.py 498 -> 100 + 5 modules
Some checks are pending
Pre-commit checks / pre-commit (push) Waiting to run
Python tests / test (push) Waiting to run
Shell tests / shell-tests (push) Waiting to run
Shell tests / arch-tests (push) Waiting to run
The last python file over the 250-line cap. Now that it sits at 100% line
and branch coverage, split along its natural seams:

  _types.py     48  the three dataclasses, so nothing imports a sibling
                    merely to name a type (which would have made a cycle)
  _render.py    69  the ANSI palette and the printers
  _config_io.py 104 variant discovery, JSONC parsing, reads and writes
  _hardware.py  106 _run and the four probes
  _plan.py      156 the pure hardware -> proposed-changes functions
  _optimize.py  100 the CLI entry: _apply_variant and main

Definitions were moved by line range rather than retyped. A throwaway
ast.dump identity check against HEAD confirms all 26 top-level definitions
are unchanged - formatting-blind, logic-sensitive.

The first attempt raised NameError: name '_BO' is not defined at runtime;
_BO was used by main() but missing from the _render import list. Rather than
fix that one failure alone, an AST scan over all six modules for names used
but not bound confirmed nothing else was missing.

Test imports now name the owning module; no re-export barrels. One patch
target genuinely moved - the guard-rail test patches _config_io.Path, since
that is the namespace resolving the name when _discover_variants is called
directly rather than through main().

Verified: same 98 tests pass with every module at 100% line and branch;
python3 -m python_pkg.vscode_optimizer --dry-run reproduces the same
18-change plan and writes nothing; repo-wide pytest 1846 passed at 100%;
over-cap files 36 -> 35.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
2026-08-17 13:15:50 +02:00
9d12c03ccc vscode_optimizer: move under python_pkg and cover 0% -> 100%
meta/scripts/optimize_vscode.py was 498 lines against a 250-line cap and
completely untested, and at that path the repo-wide gate (--cov=python_pkg,
fail_under = 100) could not see it. Moved verbatim to
python_pkg/vscode_optimizer/_optimize.py - git records a pure rename, and
the content is byte-identical to HEAD.

The move and the tests are one commit by necessity: any intermediate state
with the module under python_pkg/ but uncovered turns every pytest run in
the repo red, including ci-mirror on pre-push for unrelated work.

Running it by path no longer works after the move (sys.path[0] would be the
module's own directory), so __main__.py replaces it, following the existing
focus_policy convention rather than a sys.path insert. Nothing invoked the
old path: grep over .sh/.desktop/.service/.yml/.py found zero callers.

98 tests reach 100% line and branch coverage. Patching is confined to real
boundaries - subprocess.run, the module-level Path, builtins.input, sys.argv
- so the forthcoming split has few monkeypatch targets to re-point.

Writing the tests surfaced a pre-existing bug, left unfixed here and pinned
by a strict xfail: _VENDOR_KW's "ati" key is a substring of "VGA compatible
controller", which appears in every lspci display line, so it matches before
"intel" is ever tried and Intel iGPUs are misreported as AMD.

Verified: python3 -m python_pkg.vscode_optimizer --dry-run reproduces the
pre-move 18-change plan and writes nothing; the real settings.json is
untouched; repo-wide pytest is 1846 passed at 100%; over-cap files 37 -> 36.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
2026-08-17 13:10:10 +02:00
ff1a1b6a0e usage_report: split usage_report.py 426 -> 101 + 195 + 117 + 68
The entry point now holds only the parser, mode dispatch and main. Three
modules take one concern each: _usage_report_logs (where the logs are,
the state file, per-day segment planning), _usage_report_emit (preflight,
stdout, clipboard), _usage_report_run (the two run paths and the
aggregation they drive).

Verified by the 100%-coverage tests from the previous commit. An AST
comparison of all 22 top-level defs and classes reports 22 identical, 0
mismatches.

All four modules hold 100% line+branch. 204 tests before and after. Both
CLI modes were run against the machine's real logs, which is the check
the unit tests cannot make: they stub the collaborators, so only a real
run proves the new import graph resolves.

Two lessons, recorded because the next target is a 498-line file:

This cut was too big. Two names had to be relocated after their first
placement created an import cycle, and four modules' worth of monkeypatch
targets needed re-pointing. A smaller first slice would have been cheaper.

Patching a moved function must target the module whose namespace resolves
it - the caller's, not the one it was moved to. Getting that wrong on
_run_since made main() call the real function, which read the actual atop
logs and hung the suite rather than failing.

Three import bugs (Path and _fmt_h left under TYPE_CHECKING but used at
runtime, dataclass missing where _Aggregates landed) were caught
immediately by the coverage from the previous commit.

Over-cap 38 -> 37.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
2026-08-17 12:46:06 +02:00
ec67387c07 usage_report: cover usage_report.py 47% -> 100%
25 tests in four files, no production change - the module is
byte-identical to HEAD. Lands before the split for the usual reason.

Covers the CLI surface (every default, every flag, main's dispatch), both
run paths including the shared empty-window-means-a-full-day fallback,
_run_since's no-data path and both state-advance branches, the
clipboard/emit side (tool selection, a failing tool, no tool at all),
_preflight's three outcomes, _aggregate_segments with a gap day and with
a present-but-empty log, and _log_descriptions.

Also covers _compute_window, which turns out to be dead: a shim
documented as "kept for backwards import compatibility" with zero callers
anywhere in the repo. Tested rather than deleted here, since removing it
is a behaviour change that does not belong in a coverage commit - but it
is a deletion candidate.

185 stmts / 44 branches, 0 missed. Directory: 179 -> 204 passing.

No suppressions: mypy rejected a `list.append(...) or 0` lambda, replaced
with a real closure; ten ruff S108 findings on hardcoded /tmp paths became
/nonexistent sentinels, which is more accurate anyway since those paths
are never opened.

Written as two files of 273 and 282 lines, then split to four under the
cap before committing - the DropZone lesson from earlier today.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
2026-08-17 12:21:21 +02:00
b5dbe1e67a usage_report: split _usage_report_parsing.py 425 -> 243 + 206
Some checks are pending
Pre-commit checks / pre-commit (push) Waiting to run
Python tests / test (push) Waiting to run
Shell tests / shell-tests (push) Waiting to run
Shell tests / arch-tests (push) Waiting to run
Lifts the atop subprocess machinery into _usage_report_atop.py: running
atop, streaming its parseable output, building/caching the atop_agg C
helper, and reading its TSV. What stays is record parsing, epoch windows
and per-day merging.

Verified by the 100%-coverage tests from the previous commit. An AST
comparison of all 16 functions against HEAD reports 16 identical, 0
mismatches.

_usage_report_render.py imported _run from the parsing module; it now
imports from the module that owns it. That is the same re-export barrel
the last split shipped and had to fix in a follow-up - caught in the same
commit this time. usage_report.py needed no edit at all.

Both modules hold 100% line+branch (94/28 and 130/34, 0 missed). 179
tests before and after; the five new test files collect 8+6+4+14+5 = 37,
which is last commit's 36 plus the one re-homed out of
test_usage_report_modes.py, where it referenced a constant that had
moved out from under it.

The two test files added last commit were themselves over cap (373, 272)
so they are split five ways here - the DropZone lesson from earlier
today, where a lint fix silently added an over-cap file.

Ran the program: a real report from today's logs, through the native
atop_agg path, which is precisely the code this commit moved.

Over-cap 39 -> 38.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
2026-08-17 12:12:11 +02:00
e165ae8987 bucket_catch: 45 eslint errors -> 5, no suppressions
These were unmasked, not caused, last session: `lint` is
`tsc --noEmit && eslint src`, and a broken tsc had been short-circuiting
the && so eslint had never run on this package.

The binding constraint is the 100% coverage gate, not the linter. Most
idiomatic fixes for no-non-null-assertion add a branch that no test can
reach, which trades a lint error for a coverage failure - and the
no-suppressions rule forbids escaping either way. So every fix here is
one that adds no branch:

- no-non-null-assertion 18 -> 2: a waitFor already proves result.current
  is non-null, so capture it into a local instead of asserting on each
  read. App.tsx's files[0]! was pure noise (the same position also
  reported no-unnecessary-type-assertion). usePuzzleGameLoop's shift()!
  became index-then-shift, since the while condition already indexes [0]
  unasserted.
- non-nullable-type-assertion-style 9 -> 0: the rule-swap trap - eslint
  asks for `!`, which the other rule forbids. Narrowed with instanceof.
- unbound-method 10 -> 1: keep the MockInstance vi.spyOn already returns
  rather than detaching the method. This surfaced a latent bug: one test
  calls restoreAllMocks() mid-test, so the beforeEach handle was stale.
- no-unnecessary-type-assertion 5 -> 1, require-await 1 -> 0.

Coverage held at 100%: 584/217/98/535 -> 585/217/98/536. The +1 statement
and +1 line are the index-then-shift split; branches unchanged at 217 is
the number that proves no fix added an untested path. 145 tests, tsc 0.

The 5 remaining are reported, not silenced. App.tsx's fileResult! needs
the phase state to carry the result ({ kind: "done"; result }) - three
smaller structures all failed the same way. The rest are documented in
the evidence file.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
2026-08-17 11:48:45 +02:00
ad646541ad usage_report: split _usage_report_render.py 310 -> 181 + 123 + 24
Some checks are pending
Pre-commit checks / pre-commit (push) Waiting to run
Python tests / test (push) Waiting to run
Shell tests / shell-tests (push) Waiting to run
Shell tests / arch-tests (push) Waiting to run
Verified by the 100%-coverage tests from the previous commit, written
before this seam existed.

_cpu_table/_dedupe_ram/_ram_table/_gpu_table move to
_usage_report_tables.py with the two constants they own. _md_escape and
_fmt_h move to _usage_report_format.py: both the tables module and the
render module need them, so a third module is what avoids a cycle.
Import sites name the owning module; no re-export barrel.

usage_report.py needed no edit - it imports _fmt_h and _render_report
from the render module, and both names still resolve there.

Evidence the move was verbatim: an AST comparison of all 10 functions
against HEAD reports 10 identical, 0 mismatches. That matters because I
retyped rather than cut-and-pasted, and did briefly introduce a literal
× where the original had × - ruff's RUF001 caught it.

Coverage stays 100% line+branch on all three (15/55/54 stmts, 4/10/14
branches, 0 missed). Test count 43 before and after, summed across the
five files it now lives in - the check that matters for a test split,
since a file outside the runner's glob is silently never collected.
Directory total 143 passed, unchanged.

Ran the real program: a full report from today's atop + pmon logs, every
section rendering through the new boundaries including the RAM sibling
dedupe and its (+20 more) truncation.

Over-cap 41 -> 39. No suppressions; a shared test-helper module was tried
and abandoned because name-tests-test would have needed an exclusion.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
2026-08-17 11:34:08 +02:00
fc185b835f billsplit: drop the four unused platform runner directories
Some checks failed
Pre-commit checks / pre-commit (push) Waiting to run
Shell tests / shell-tests (push) Waiting to run
Shell tests / arch-tests (push) Waiting to run
billsplit CI / quality (push) Has been cancelled
windows/ (18 files), macos/ (28), ios/ (40) and linux/ (10) were flutter
create scaffolding that nothing in this repo builds: billsplit-ci.yml runs
pub get, dart format, analyze, test and the Python coverage gate, with no
flutter build step for any platform, and no script or doc invokes one
either. 96 tracked files, 4188 lines.

This also removes win32_window.cpp (288 lines), the last over-cap C++
file. It could not be split honestly - flutter regenerates it - so
deleting the target it belongs to is the honest way off the list.
Over-cap: 41 -> 40, and the cpp category is now empty.

Unpicked all three references: the .binary-allowlist icon entries and the
.gitignore un-ignore rules that existed only to let those icons past the
binary block, plus the README claim that the repo ships without platform
runner folders (false while 96 were tracked, true again now).

The four dirs are now gitignored so `flutter create` regenerates them
locally without re-tracking them. Verified by actually running
`flutter create . --platforms=linux,windows,macos,ios`: git add -An then
refuses all four as ignored. analyze/test/format stay green, 60 tests.

analysis_options.yaml is deliberately untouched - flutter analyze rewrites
the platform excludes back on every run, so removing them is not durable.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
2026-08-17 11:14:32 +02:00
e58eec2b6a billsplit: fix the two CI gates that were already red on main
flutter analyze --fatal-infos exited 1 at 6713ef3 with four
unnecessary_unawaited issues in lib/ui/home_screen.dart: Flutter now
annotates Navigator.push with @awaitNotRequired, which makes the
unawaited() wrappers redundant. Removed all four wrappers and the
dart:async import they were the only user of.

dart format --set-exit-if-changed was separately red on three test files
left unformatted by a68313f. One trailing blank line each.

Both are steps in .github/workflows/billsplit-ci.yml, so billsplit CI was
failing on main for reasons unrelated to any current work. Fixing them
first means the platform-directory deletion that follows can be verified
green-to-green.

flutter test: 60 passing before and after - the unchanged count is what
proves the unawaited() removals did not shift navigation timing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
2026-08-17 11:11:04 +02:00
da1e0d3588 atop_agg: split atop_agg.c 478 -> 204 + state + emit
Three units along the file's own concerns:

  atop_agg.c (204)        ingest -- tokenising atop output, folding lines, main
  atop_agg_state.c (113)  the per-PID hash tables, slot lookup, epochs, alloc
  atop_agg_emit.c (178)   the ranked, name-aggregated report

Table geometry and the four table functions that now cross a unit boundary go
in a private atop_agg_internal.h. Those four lose `static` -- the one
visibility change here.

The Makefile edit is the load-bearing part: SRC had to grow or the build would
have linked the old single unit and the suite would have passed with the new
files unused. Checked by reading the compile line, not the exit status.

I had written this file off as unverifiable earlier. Wrong: the Makefile has
had test and coverage targets all along. Coverage is 92.9% lines / 100%
functions on the same 211 source lines, before and after -- the best verifier
of anything left over the cap. Production binary builds at -O2 and runs.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
2026-08-17 10:49:46 +02:00
c2760ef5e3 Split reverse_survivors types.ts 295 -> 157 + roster + statuses
The 82-line ENEMY_SPECS table and its kind unions are data, not vocabulary --
that is the block you edit to balance the game. Moved to enemies.ts (143).

statusKinds.ts (25) is not cosmetic: enemy specs name the status they inflict
and the survivor carries seconds-remaining per status, so leaving StatusKind
in types.ts would have made types.ts and enemies.ts mutually dependent. It
imports nothing, so the graph is statusKinds <- enemies <- types.

14 files repointed by script rather than by hand, splitting each types import
across the three owning modules by symbol.

The tests cannot catch a mistyped stat -- they assert behaviour, not the
balance table, so a wrong enemy hp passes 160 tests at 100% coverage. Checked
separately: 191 numeric pairs before, 191 after, compared as sorted multisets,
identical. check and build both pass; bundle 210.11 kB, same as baseline.

No reverse_survivors file is over the 250-line cap now.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
2026-08-17 09:57:26 +02:00
1342a60a65 Split reverse_survivors sim.ts 361 -> 69 + three modules
Some checks are pending
Pre-commit checks / pre-commit (push) Waiting to run
reverse-survivors CI / check (push) Waiting to run
Shell tests / shell-tests (push) Waiting to run
Shell tests / arch-tests (push) Waiting to run
Split along actor lines, but not the obvious way. A plain sim/enemies cut
needs makeEnemy and grantXp going out and enemiesStep coming back -- a cycle.
Nothing here would have caught it: no eslint-plugin-import, and tsc and vitest
both pass on cyclic ES modules. It shows up as a TDZ error in the bundle.

So the graph is a tree instead:

  simUnit.ts (42)      leaf -- geometry, projectile constants, makeEnemy,
                       enemySpeed; imports only types and vec
  simSurvivor.ts (132) createSurvivor, xp/upgrades, survivorStep
  simThreats.ts (168)  enemiesStep, projectilesStep, and enemy death
  sim.ts (69)          createInitialState and step, the only module that
                       reaches all three

Repointing director.ts's makeEnemy at the leaf also removes the sim <->
director cycle that was already there, surviving on const-arrow late binding.

npm run check and npm run build both pass -- 160 tests, coverage identical at
531/159/101/514, 32 modules bundled at 210.09 kB against 210.11 kB before.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
2026-08-17 09:52:19 +02:00
44b8b79207 Split kcd2 App.tsx 306 -> 217 + useInventoryState
App.tsx owned the inventory as well as rendering it: resolving a shared link
against this browser's storage at mount, deferring persistence until the
visitor consents, clearing the URL fragment, and three mutators. Moved all of
it to a useInventoryState hook (163) beside the existing useSolver.

The two inline handlers that only set state became named keep/replace
callbacks, so the view no longer touches setPersist or setSaved; Clear,
Discard and onImport all go through replace.

Coverage held at 100% -- totals moved 863/375/181/810 -> 864/375/182/811,
exactly the one statement and one function that promoting the Keep handler
adds. Branches unchanged, so nothing conditional left test reach. That was
the metric that would have caught an over-large extraction here, since the
hook has no dedicated test and rides on the four App test files.

No kcd2_dice_solver file is over the 250-line cap now.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
2026-08-17 09:46:49 +02:00
47996ac017 Split kcd2 badgeValue.ts 334 -> 172 + baseline
The module did two things: measure what a badge is compared against, and value
each badge against it. Moved the first half to badgeBaseline.ts (185) -- the
shared types, measureBaseline's 4,000-turn simulation, turnValue,
bestThrowsValue, singleCharge, rankValue and the context defaults.

badgeValue.ts (172) keeps valueBadge's per-effect switch and recommendBadges.
The dependency runs one way, and valueBadge's default parameter still calls
measureBaseline, so callers that omit a baseline behave as before.

solve.ts and badgeValue.test.ts were repointed, import paths only. Coverage
totals unchanged at 863/375/181/810, so every arm of the valuation switch is
still exercised.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
2026-08-17 09:43:06 +02:00
d39ad67323 Split kcd2 scoring.ts 361 -> 172 + move generator
The 114-line private moves() method was most of the file: it enumerates every
legal step from a position -- straights, badge formations, n-of-a-kind, lone
1s and 5s, and the two joker rules -- while Scorer just maximises over it and
caches the answers.

Extracted it to scoringMoves.ts (203) as a free function over ScoringConfig,
taking combinationValue with it (its only caller, and the home of the
Emperor/Tyche triple-only rule) plus the tripleBase/ofAKindValue helpers.
scoring.ts (172) keeps Scorer, its two caches and its two recursions.

moves() was private, so no consumer saw the seam; only scoring.test.ts moved,
for the two value helpers, import path only. Coverage totals are unchanged at
863/375/181/810 -- the generator's branches are all still exercised.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
2026-08-17 09:39:31 +02:00
1d833d7524 Split kcd2 search.ts 417 -> 100 + two modules
Some checks are pending
Pre-commit checks / pre-commit (push) Waiting to run
Shell tests / shell-tests (push) Waiting to run
Shell tests / arch-tests (push) Waiting to run
search.ts held three separable concerns. Extracted two:

  searchGroups.ts (109)     the vocabulary -- InventoryEntry, DiceGroup,
                            Selection -- plus groupInventory, countCandidates
                            and expand
  searchStrategies.ts (232) Leaderboard and both strategies: exhaustive
                            enumerateAll, and hillClimb with its greedy and
                            random seeds

search.ts (100) keeps findBestSet, which picks between the strategies.

Import sites were rewritten to name the owning module rather than leaving a
re-export barrel in search.ts, so two test files changed -- import paths only,
no assertion touched. DiceGroup and Selection had to become exported to cross
the module boundary.

Function bodies moved verbatim: 288 tests still pass and coverage totals are
identical at 863/375/181/810, not merely still 100%.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
2026-08-17 09:26:42 +02:00
4eedc17b17 Split install_pacman_wrapper.sh into an entry plus four libs
The 316-line installer was 66 over the 250-line cap. The managed-copy helpers,
the /usr/local/bin install step, the integrity + drift manifest, and the
immutability/LeechBlock/VirtualBox payloads move into a sibling lib/, leaving a
128-line entry.

What stays in the entry is chosen by what the harness can execute, not by
taste. `--bind-abs /usr/bin` removes the directory bwrap needs to exec
anything, so the trace stops at `ln -sf ... /usr/bin/pacman` with exit 1 on
EPERM. That line onwards, plus the `if [ ! -L /usr/bin/pacman ]` backup branch
whose guard is false on this machine, therefore never run under trace — moving
them would have shipped code no diff could check. They stay put.

Everything else is proven: the before/after traces are identical, including all
16 generated-file hashes. pacman_wrapper at 548fc2b53ee4cea6 is the one that
matters most — it is post-`sed -i`, so it catches a reorder of the rewrite
relative to the copy.

The path definitions stay in the entry too, for a linter reason rather than a
runtime one: a definitions-only lib assigns without referencing, which is
SC2034 on 36 variables, and suppressions are not allowed. Libs that only
reference globals are fine. `export` would have silenced it while leaking 40
variables into every child process, which the trace cannot see.

Eight assertions across two test libs grep the installer's source text; five
needed widening to search entry+lib/. TEST 9's `chattr +i` grep caught the move
as a genuine failure first, which is what prompted enumerating the rest. No
assertion was weakened.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
2026-08-17 07:42:31 +02:00
26965bad32 Split install_usage_monitoring.sh into an entry plus five libs
The 290-line installer was 40 over the 250-line cap. Its five banner-marked
steps move into a sibling lib/, leaving a 53-line entry that owns log()/die(),
the globals that cross between steps, and the call order.

Proven by execution, not inspection: meta/scripts/trace_shell_split.sh
--prefix was captured at HEAD in place (tracing from a detached worktree
changes REPO_DIR, which is interpolated into a generated file, and fakes a
diff), then re-run after the split. The traces are identical -- same exit
status, same ordered stubbed calls, and all five generated-file hashes
unchanged, including usage-report-catchup.sh at sha=9f4803ab157c5527.

REPO_DIR is computed once in the entry and read as a global rather than
recomputed inside a lib one directory deeper; recomputing it would have
pointed the generated catch-up script at the wrong repo while still exiting 0.

test_usage_monitoring_installer_efficiency.sh greps the installer's source
text for the pmon heredoc, so it follows that heredoc to lib/nvidia_pmon.sh.
Its extraction anchor also had to become whitespace-insensitive: the
pre-commit hook runs `shfmt -w` on staged shell files and rewrites
`cat > "x" << 'SCRIPT'` to `cat >"x" <<'SCRIPT'`, which silently broke
extraction. All five property assertions are unchanged.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
2026-08-17 07:26:56 +02:00
f6356d4725 Trace scripts that place files, via a prefix redirect
Some checks are pending
Pre-commit checks / pre-commit (push) Waiting to run
Shell tests / shell-tests (push) Waiting to run
Shell tests / arch-tests (push) Waiting to run
Shell redirections are builtins, so no PATH stub can intercept
`cat > /etc/modprobe.d/x.conf`. That left installer scripts unverifiable:
stub cp/chmod/ln and the trace is nearly empty, don't and the run rewrites
the live system.

--prefix redirects HOME and every XDG_* base into a temp tree. XDG_DATA_HOME
is exported explicitly, not left to default off HOME: install_leechblock.sh
reads it directly and then runs `rsync -a --delete` at it.

--bind-abs binds hardcoded absolutes via bwrap --unshare-user --uid 0, leaf
directories only. uid 0 is load-bearing beyond permissions: common.sh's
require_root does `exec sudo "$0"`, and under the sudo stub that truncated
the whole run to one recorded call -- a trace that diffs clean against any
split. Binding /etc wholesale was measured to shadow /etc/passwd, collapse
$SUDO_USER to /home//, and mask a nested bind, so the scanner never emits it.

The trace gains a manifest of every file written, with content hashes. That
section is the point: with it, a dropped write in the fixture pair is a
one-line diff; with it stripped, the two traces are identical, i.e. the old
harness passed that broken split. Hashes normalize the prefix to @PREFIX@
because install_usage_monitoring.sh writes an unquoted heredoc that bakes
$HOME into the artifact.

Verified: ~/.config/systemd/user, ~/.local/bin, ~/.local/share and
/etc/modprobe.d byte-identical before and after; usage-report units still 2.
The three prior regressions are now committed fixtures rather than ad-hoc --
one was silently neutered mid-change when a trailing echo added for SC2034
became the function's last statement and turned exit 1 into exit 0.

Stub generation moved to lib/ because --prefix pushed the harness to 252
lines, over the cap it exists to serve.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
2026-08-16 23:31:49 +02:00
97a722d2b2 Split lint_python.sh: output helpers and the full-mode linters
353 -> 175 + 116 + 96. The print_* helpers, run_tool, check_tool and the
--help text move to lib/lint_output.sh; everything that runs after
--quick would have exited moves to lib/lint_full_tools.sh as
run_full_mode_linters. The output lib is sourced before argument parsing
because --help now calls usage() from it.

Verified on the path that actually contains the change: a full-mode run
diffed byte-identical against a detached worktree at the pre-split
commit, with pylint, flake8 and pycodestyle in the output proving the run
entered the extracted block. The --quick comparison is also identical but
exits before that block, so it alone would have proved little. The
failure path returns 1 on an unformatted file, confirming OVERALL_STATUS
still propagates out of the function.

Two shellcheck findings fixed rather than suppressed: SC2034 on
OVERALL_STATUS (the lib is analysed standalone, so the cross-seam
variables are now declared with a comment), and a pre-existing unquoted
`exit ${OVERALL_STATUS}` that the commit hook's stricter settings
surfaced. lint_python.sh was shfmt-clean before and still is.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
2026-08-16 23:08:02 +02:00
c4bdaad038 Split the last two over-cap shell test suites
test_pacman_wrapper_security.sh (287) and test_i3blocks_efficiency.sh
(345). Both keep their filenames -- shell-tests.yml runs them by name --
and both were verified by diffing a full run against the pre-split code:
byte-identical output, exit 0 either side.

The i3blocks setup half stays in the entry on purpose. Its five fake
$BIN_DIR binaries are written with heredocs, and a seam inside a heredoc
yields a lib that will not parse -- the worst failure this effort has
produced so far. Only the two heredoc-free regions moved.

Both i3blocks lib functions end in an explicit `return 0`. Their last
statement is a `|| fail ...`, which as a function tail under set -e
becomes the return value and would abort a run in which every check
passed. `fail` exits on its own, so reaching the end means success.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-16 22:27:44 +02:00
656375275b Split test_security_hardening.sh; finish the mutation-driver split
Two files under the cap.

test_security_hardening.sh (423) keeps its filename -- shell-tests.yml
runs the suite by name -- and its five sections move into three sourced
libs. They are top-level blocks, not functions, so each is wrapped and
called in the original order; the counters and LIVE_SECTION stay in the
entry, which sourcing shares. Verified by diffing a full run against the
same suite from a detached worktree at HEAD: byte-identical output, same
exit status, still 18/1/5 on this host. The one failure is pre-existing.

Linting a lib alone turns a bare LIVE_SECTION=1 into SC2034, since its
only reader is test_result in the entry. A live_section() setter makes
the write a real reference, so no suppression was needed.

The mutation-driver split was already in the tree from the previous
session and did not run: the operators had moved to
_session_autopsy_mutations.py but the driver still named Mutation
without importing it, so mypy and pylint both reported undefined-variable.
Restored the import; the script now reports all 12 tests killed.

block_compulsive_opening.sh (705) is deliberately untouched and now says
so in the contract. install_all copies the running script to
/usr/local/bin, so an entry sourcing lib/ would resolve SCRIPT_DIR there,
find nothing, and leave beeper, signal-desktop and discord unlaunchable
with a failing pacman hook. Splitting it means teaching the installer to
copy the libs -- a refactor, not a line-count split.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-16 22:24:02 +02:00
1b19808edb Keep this session's splits shfmt-clean
analyze_repo.sh was shfmt-clean at 133fccf and my split made it dirty
(a double blank line). Fixed with shfmt -w, then restored the STEP 1
comment banner shfmt -w also stripped -- all eight banners re-counted.

The three new shell_check libs inherited their parent's 2-space
indentation; as new files they have no prior style to preserve, so they
are now tabs like shfmt wants.

Context for the exit code: 157 of 298 linux_configuration shell files
already fail shfmt at 133fccf. shfmt is not in .pre-commit-config.yaml
and there is no .editorconfig, so run_linters exiting 1 on the full tree
is the repo's standing state, not a regression here. Scoped this commit
to what these splits touched rather than reformatting 157 files.

Also closes the risk left open in a84556a: run_linters now verified
directly over the three new libs -- all six passes PASS, exit 0 -- with
install_linters never invoked, so decision 6 holds. That is the path
.github/workflows/shell-check.yml runs.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
2026-08-16 22:05:01 +02:00
a84556ac2a Split shell_check.sh into an entry script and three libs
428 lines -> 121, with lib/shell_check_lint.sh (171),
lib/shell_check_install.sh (86) and lib/shell_check_discover.sh (78).

The temp-file paths (TMPDIR, ABS_FILES_Z, REL_FILES_Z) are set once in
the entry before any lib is sourced and only read afterwards, so nothing
mutable crosses a seam. shellcheck was clean on the first attempt.

Runtime check is --skip-install --list-only --verbose, diffed against the
same run from a detached worktree at HEAD: 318 files before, 321 now,
the delta being exactly the three new libs the discovery walk now sees.
Nothing removed.

Not verified: the linting path itself. --skip-install does not gate the
AUR branch of install_linters, so a full run cannot be done under
decision 6 -- I learned that by running it, which installed four AUR
build deps before I killed it. The user chose to leave them. run_linters
moved as one intact block with no interior edit and the line-set diff
shows zero lines lost, so the seam risk is confined to its two braces.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
2026-08-16 21:57:19 +02:00
d889c8e655 Split music_parallelism.sh into an entry script and three libs
Some checks are pending
Pre-commit checks / pre-commit (push) Waiting to run
Python tests / test (push) Waiting to run
Shell tests / shell-tests (push) Waiting to run
Shell tests / arch-tests (push) Waiting to run
412 lines -> 135, with the nine functions in lib/music_detect.sh (133),
lib/music_monitor.sh (91) and lib/music_status.sh (85).

find_music_services and kill_music_services stay together: the first
writes MUSIC_FOUND_PROCESS/MUSIC_FOUND_WINDOW and the second reads them,
so splitting between them would thread mutable state across files.

The test suite caught what the static checks could not. The harness
copies the script into a temp worktree to stub common.sh, and copied
exactly one file -- so after the split every source line failed and the
script died before any behaviour under test ran. Teaching it to copy
lib/ too is a harness fix under decision 8: no assertion changed, the
same five checks run, exit 0 before and after.

bash -n also earned its keep: the first splice dropped notify_user's
closing brace, and shellcheck reported that only as SC1094 "parsing of
sourced file failed" against the entry.

One rename, not a suppression: monitor_loop's local `music_services`
became `active_services` after shellcheck flagged SC2153 against the
MUSIC_SERVICES config array now visible in the same file.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
2026-08-16 21:50:34 +02:00
41cc0451c9 Split analyze_repo.sh into an entry script and five libs
869 lines -> 231, with the extracted helpers and per-STEP blocks in
lib/analyze_repo_{scan,tools,separate,report,symbols}.sh (86-228 each).

The seam that mattered was STEP 4, whose two passes share a map of
language -> temp file. Rather than let that cross a file boundary, the
map is passed by nameref and the comments file by parameter, so
separate_code_and_comments fills what report_languages reads with no
global in between.

Two boundaries were one step off on the first pass and produced 23
shellcheck findings: the KEYWORDS_* tables and detect_languages had been
cut away from their only consumers, so every one read as unused (SC2034)
or unassigned (SC2154). Moving each beside its consumer cleared all 23
without a suppression. The nameref also makes shellcheck read a bare
assoc-array subscript on the left of an assignment as arithmetic;
quoting the nine keys is the fix.

The line-set diff against HEAD earned its place here: it caught a
`find ... -printf` call at original line 719 that had fallen into a
one-line gap between two extraction ranges and been dropped. bash -n and
shellcheck both passed on that broken version.

Verified without running the script (decision 6 -- install_missing_tools
calls sudo pacman, cargo and makepkg): shellcheck clean across all six,
the function set matches HEAD plus exactly the six new wrappers, no
executable line lost, and a stubbed run of the entry preamble defines
all fourteen functions, proving the source lines resolve at runtime.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
2026-08-16 21:47:03 +02:00
c05843153d Split setup_dns_blocker.sh, a live systemd unit target
569 -> 211 across three libs. All seven heredocs sit inside functions,
so function boundaries were safe seams.

The script guards its entry with SETUP_DNS_BLOCKER_SKIP_MAIN, which made
a real function-set comparison possible on a script that would otherwise
reconfigure the machine when sourced: an identical 74 names against a
detached worktree at HEAD. The dns-blocklist-refresh unit still resolves
to it and it still parses.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
2026-08-16 21:31:03 +02:00
af3e4a9b13 Split update_android_hosts.sh; extract its blocklist as data
870 -> 146 across three libs, plus 247 lines of hosts entries moved from
a heredoc into data/android_guardian_blocklist.hosts. A data file draws
no linter, unlike extracting an embedded Python program.

The installed blocklist is byte-identical: md5 7d3f2550 on both sides.

The first attempt cut inside the `case` dispatcher and neither the entry
nor a lib parsed. Redone with the case as a hard boundary - this script
has no main(), so the case block and the argument parsing that feeds it
stay in the entry.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
2026-08-16 21:23:39 +02:00
ef0a26abc7 Split scripts/lib/mtk_common.sh with the same entry-point pattern
503 -> 132 across three parts. The seven mtk_root consumers are
untouched because the path they source is unchanged.

Identical 28-function set before and after, checked after each of the
two carves. The `<<<` in this file are here-strings, not heredocs -
worth confirming rather than assuming, after enforce_vbox_hosts.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
2026-08-16 21:17:50 +02:00
51e3945507 Split scripts/lib/common.sh, the library 49 scripts source
Some checks are pending
billsplit CI / quality (push) Waiting to run
Pre-commit checks / pre-commit (push) Waiting to run
Shell tests / shell-tests (push) Waiting to run
Shell tests / arch-tests (push) Waiting to run
620 -> 163, with common.sh staying the single entry point that sources
its own three parts - the shape the handoff prescribes for config.sh, so
all 49 consumers are untouched.

Verified by diffing `declare -F` after sourcing it against the same from
a detached worktree at HEAD: an identical 48-function set. For a library
this widely sourced that is the check that matters, not a passing lint.

The FOCUS_APPS_* arrays moved with is_focus_app_running, the only
function that reads them.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
2026-08-16 21:14:48 +02:00
a68313f236 Split every over-cap billsplit Dart file
Four lib files and the 536-line UI flow test, into seven new files. Only
Flutter's generated windows/runner/win32_window.cpp remains, and editing
that would be undone by regeneration.

All four lib splits used part/part of first time, applying the rule
learned in focus_owner. models.dart needed a hand fix: it has a
`library;` directive, which must precede any `part`.

60 tests passing before and after. flutter analyze reports the same 4
pre-existing infos as a detached worktree at HEAD - which is how I
caught that an intermediate run's 7 issues were 3 of my own.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
2026-08-16 20:57:56 +02:00
d9982e53dd Split the focus_owner Dart files under the cap
main.dart 538 -> 130, policy.dart 272 -> 85, enforcement_status_card.dart
268 -> 145, plus both test files.

Every lib split uses part/part of rather than import. The first attempt
used import+export and the analyzer gave 30 errors like "The name
'_Card' isn't a class": Dart privates are visible to the whole LIBRARY,
and a separate file is a separate library unless it is a part.

The test splits separated groups from the testWidgets() calls that had
been initialising the binding implicitly, so four tests failed with
"Binding has not yet been initialized" - both halves now call
ensureInitialized() explicitly.

94 tests passing before and after; flutter analyze reports no issues.
focus_owner/analysis_options.yaml has an uncommitted change from an
earlier session and is deliberately left unstaged.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
2026-08-16 20:39:13 +02:00
093e2f6f68 Split the reverse_survivors test files; defer its two core sources
sim.test.ts 698 -> 175 and director.test.ts 315 -> 178, into four new
modules. 160 tests passing before and after.

sim.ts and types.ts are reverted. sim.ts's exported step() calls
survivorStep, which any seam moves out - 63 tests failed with a
ReferenceError while tsc reported only unused imports. types.ts has its
difficulty tables below the type declarations that use them.

Third project now where a TypeScript seam compiles clean and fails at
runtime. In TS, tsc does not tell you a split worked; the test run does.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
2026-08-16 20:26:59 +02:00
45b56b3e61 Split the Pi flasher and the boot-repair test harness
raspberry_pi_flash_sd.sh 661 -> 166 and test_boot_repair.sh 315 -> 104.
Heredoc positions were checked before choosing seams this time.

test_boot_repair.sh is the first linux_configuration script in this
effort that could be verified by running it: every case builds a
throwaway root in a temp dir and passes --root. 31 passed after the
split, and 31 passed from a detached worktree at HEAD - a real baseline
comparison rather than trusting the post-split number.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
2026-08-16 20:21:44 +02:00
0aa78ea7aa Split three more scripts; make the verifier catch heredoc cuts
Some checks are pending
Pre-commit checks / pre-commit (push) Waiting to run
Shell tests / shell-tests (push) Waiting to run
Shell tests / arch-tests (push) Waiting to run
transcribe.sh 473 -> 39, setup_activitywatch.sh 457 -> 102 and
setup_wireguard_ssh.sh 464 -> 136.

enforce_vbox_hosts.sh is reverted: its seam landed inside a heredoc and
the resulting lib had an unterminated here-document. That is the worst
failure yet, because shellcheck reported it only as SC1094 on the caller
- which reads like a nicety, not a file that will not parse. The
verifier now runs bash -n over every lib before linting the entry and
names the offending file; proven by corrupting a copy and restoring it.

Also guarded a cd before `git clone ... .`, which would otherwise have
cloned an AUR repo into the caller's directory.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
2026-08-16 20:16:08 +02:00
63650fbc45 Split offline docs and arch optimiser; defer libre_translate
setup_offline_docs.sh 713 -> 121 across six libs, carved one language at
a time since the first cut left offline_cpp at 545. optimize_arch_
desktop.sh 623 -> 118.

Two recurring traps again: a script with no SCRIPT_DIR at all, and libs
that chained rather than being sourced from the entry. Both now checked
for routinely.

libre_translate.sh is reverted and stays at 488. Exporting GENERATED to
fix one SC2034 revealed eighteen more - when a fix uncovers more of the
same, the seam is imaginary, not the fix wrong.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
2026-08-16 20:09:54 +02:00
776e04db89 Split the kcd2_dice_solver test files under the cap
Nine new modules from four over-cap test files. Every kcd2 test file is
now under 250 lines; only four source files remain.

App.test.tsx ended up with no describe blocks and vitest failed with
"No test suite found" - a split that empties a file has to delete it.
The shared `response`/`ordinary` fixtures went to src/test/fixtures.ts,
which vite.config.ts excludes from coverage; beside the source they
would have counted as production code and broken the 100% gate.

288 tests passing before and after, now across 24 files instead of 15,
coverage still 100% on all four metrics.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
2026-08-16 20:03:33 +02:00
5a27acd9d0 Split kcd2 simulate and inventoryIo; revert two seams
Some checks are pending
Pre-commit checks / pre-commit (push) Waiting to run
Shell tests / shell-tests (push) Waiting to run
Shell tests / arch-tests (push) Waiting to run
simulate.ts 384 -> 226 and inventoryIo.ts 287 -> 178, with the moved
names re-exported so no importer or test changed.

search.ts and badgeValue.ts are reverted. findBestSet calls enumerateAll
and hillClimb which call back into expand; valueBadge and format are
mutually recursive with the recommendation half. Splitting either makes
the two files import each other.

The split also exposed a TypeScript hazard worth knowing: with the local
Selection type no longer imported, tsc silently resolved it to the DOM
global and complained about anchorNode and focusNode rather than saying
the name was missing.

288 tests passing before and after, coverage still 100%.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
2026-08-16 19:55:52 +02:00
c04001329e Split the kcd2_dice_solver data tables under the cap
dice.ts 415 -> 93 and badges.ts 369 -> 111, with the literal tables in
six new modules. Every previously exported name is still exported from
the same path, so no importer or test changed.

Two import cycles on the way, both of which tsc and eslint called clean
and which only showed up as a runtime TypeError when 11 test files
failed with "Cannot read properties of undefined (reading tin)".
HEADSTART_POINTS now has its own module so both halves of the badge
table can use it at init time.

288 tests passing before and after, coverage still 100% on all four
metrics with the new modules measured.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
2026-08-16 19:50:54 +02:00
7a74804786 Split three more linux_configuration giants
raspberry_pi_nextcloud.sh 1276 -> 163, root_bl9000.sh 1116 -> 211,
lookup_docs.sh 942 -> 165. Sixteen new libs.

lookup_python was one 300-line function; its keyword-to-anchor mapping
became python_keyword_doc, emitting result and desc tab-separated so the
caller keeps its control flow.

bl9000_mtkclient had cd X; work; cd - with three return paths.
install_mtkclient became a subshell; extract_boot_with_mtkclient could
not, since it exports BOOT_IMG - so the venv check moved before the cd,
the cd is guarded, and the restores use a saved $PWD.

Four more unguarded cd calls fixed, and a third instance of the lib
chaining bug flattened. lookup_docs.sh had no SCRIPT_DIR at all.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
2026-08-16 19:43:55 +02:00
5bacfa3883 Split setup_nextcloud_raspberry.sh 1383 -> 231
Some checks are pending
Pre-commit checks / pre-commit (push) Waiting to run
Shell tests / shell-tests (push) Waiting to run
Shell tests / arch-tests (push) Waiting to run
Eight libs from the largest script in linux_configuration: five in the
first pass, then three of those carved again since they landed at 272,
292 and 277.

Fixed three more unguarded cd /var/www/nextcloud calls, each right
before an occ command - the installer would have run against whatever
directory the caller was in. Third script with this bug.

check_and_enable_services.sh is reverted and stays at 1337. Its
SERVICE_STATUS array is declared once and written by every check_*
function, so any lib holding them fails SC2034 alone. Fourth file
deferred for shared state, and the clearest: the script is an
accumulator, so there is no seam anywhere in it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
2026-08-16 19:37:07 +02:00
346eb1f00b Bring every markdown file under the cap
Twelve prose violations to zero. Eight files split into per-topic
subdirectories with the original left as an index; both SKILL.md files
keep their frontmatter so discovery still works, with detail moved to
references/.

Deleted docs/cleanup-2026-05/ (7 files) and linux_configuration/docs/
VERIFICATION.md on the user's ruling: unreferenced records of finished
work whose rules are now enforced by the polling, shellcheck and shfmt
hooks. PACMAN_WRAPPER_SECURITY.md was split instead - unlike those, it
documents live guarantees.

Every split verified by a sorted diff against HEAD: zero content lines
lost, only the new headings and index links added.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
2026-08-16 19:25:59 +02:00
194b006453 Split fix_bluetooth.sh 720 -> 186; defer steam_compatibility.sh
Some checks are pending
Pre-commit checks / pre-commit (push) Waiting to run
Shell tests / shell-tests (push) Waiting to run
Shell tests / arch-tests (push) Waiting to run
Four libs written in one pass rather than four sequential splits, which
is what stops the libs chaining - the entry gets one source block naming
all four and no lib sources a sibling.

steam_compatibility.sh is reverted and stays at 663. Its CACHE_MAP
associative array is declared and filled in what would be the ProtonDB
lib but read by main, so that lib fails SC2034 linted alone. Third file
deferred for the same reason: shared mutable state means the seam is not
there.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
2026-08-16 19:17:05 +02:00
d9c315e3ef Split setup_searxng.sh 912 -> 184
Six libs: the script has four phases and write_stack alone was 342 lines
because it emits three config files from heredocs. That became three
functions, one per file, each keeping the comment that explains it -
including why settings.yml is mode 640 and not 600.

For a script whose job is writing config, "the functions still exist" is
not enough. Compared the sorted non-comment lines of the original
write_stack against its three successors: identical hash, so every line
of emitted compose, settings.yml and limiter.toml is byte-for-byte the
same and only the wrappers were added.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
2026-08-16 19:11:40 +02:00
2527fb37e6 Split three large linux_configuration scripts, defer a fourth
organize_downloads.sh 513 -> 72, setup_kcd2_dice_solver.sh 497 -> 112,
fix_ubuntu_performance.sh 494 -> 150. First 490+ line scripts done; all
three needed two or three seams.

Repeated splitting chained the libs - each sourced the next, because the
tool inserts its source line relative to the file it just wrote.
Flattened so the entry sources all three and no lib sources a sibling.

diagnose_pacman_hook_stall.sh is reverted and stays at 493. Its
transaction runner assigns LAST_ELAPSED that the caller reads and reads
PACMAN_BIN that the caller defines, so linted alone it is SC2034 plus
SC2153. The pre-commit hook lints each lib in isolation and fails, while
shellcheck -x on the entry passes - suppressing that would be papering
over a seam that does not exist.

organize_downloads.sh is the ExecStart of media-organizer.service; the
unit still resolves and the script still parses.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
2026-08-16 19:06:16 +02:00
7e967de79b Split seven more linux_configuration scripts
Some checks are pending
Pre-commit checks / pre-commit (push) Waiting to run
Shell tests / shell-tests (push) Waiting to run
Shell tests / arch-tests (push) Waiting to run
10-recon.sh, install_exercism.sh, fix_arch_performance.sh,
format_sd_card.sh, setup_dot_resolver.sh, fix_unity.sh and
20-dump-stock.sh, all now under the cap.

Splitting a file twice is the risky move: the second cut on
20-dump-stock.sh dropped the first cut's source line, leaving
verify_dump called but never defined, and the repair then landed the
line inside the lib instead of the entry script. Both fixed - the entry
sources both libs and no lib sources another. Grep the source lines
after any double split.

fix_unity.sh's desktop_dir is read across the seam; renamed to
DESKTOP_DIR to match the file's own convention for globals.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
2026-08-16 18:56:05 +02:00
58fdc6c9de Split five more linux_configuration single_use scripts
install_unreal_mcp.sh 395 -> 155, diagnose_arch_performance.sh 377 ->
123 (two cuts), setup_local_llm.sh 370 -> 163, repo_to_study.sh 365 ->
160, setup_dwm.sh 359 -> 147.

The SCRIPT_DIR fix from the last batch held on all five. Fixed a second
unguarded cd, this time before the study-material generator ran - the
same latent bug class as build_website, surfaced because a moved
function gets linted in isolation.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
2026-08-16 18:51:28 +02:00
1a75ed5bf5 Split three more linux_configuration feature and fix scripts
setup_syncyomi.sh 408 -> 138 (two cuts), setup_personal_website.sh
396 -> 192, fix_thorium.sh 377 -> 208.

All three already declared SCRIPT_DIR and the splitter added a second
one, which aborts at runtime with "readonly variable" while bash -n and
shellcheck stay clean. The stubbed run caught it; the splitter now
reuses an existing declaration.

Fixed SC2164 in build_website rather than suppressing it: a bare
cd "$WEBSITE_SRC" inside a subshell, with pnpm install and pnpm run
build after it. A failed cd would have built in the caller's directory.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
2026-08-16 18:48:01 +02:00
9e62430b8d Split two more linux_configuration scripts; revert a third
control_from_mobile.sh 415 -> 180 and setup_periodic_system.sh 408 -> 228.

setup_periodic_system.sh has no main(): it defines functions then runs a
block of top-level calls, and the splitter moved that block into the lib,
changing when it ran. Redone by hand moving only definitions. It also
asks for sudo at source time, so the stubbed run is off-limits - verified
instead that the source line precedes the first call and all eleven
invoked functions are defined exactly once across the two files.

clean_audio.sh is reverted and stays over the cap. Two probe-result
globals are read from what would be two different libs, and renaming
them to satisfy SC2154 only moved the warning along. That is a refactor,
not a line-count split, and forcing it would have cost a suppression.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
2026-08-16 18:44:18 +02:00
658988ef9b Split three near-miss linux_configuration scripts
turn_off_auto_idle_screen_shutdown.sh 289 -> 180, mtk_root/install.sh
262 -> 141, setup_gitea.sh 255 -> 198. A deliberately small batch to
check jscpd before scaling; it stayed well under 2%.

The stubbed run earned its place again: mtk_root/install.sh aborted with
"SCRIPT_DIR: readonly variable" because it already declared one, and
both bash -n and shellcheck were clean on that broken file.

Two shellcheck findings the split exposed are fixed at the source, not
suppressed: persist_with_systemd_logind now takes the flag as an
argument instead of reading the caller's global, and a dead python_bin
assignment is gone. Neither was introduced here - the pre-split file
already had three SC2034 warnings.

No script was executed; all three are installers or enforcement.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
2026-08-16 18:38:43 +02:00
4b2cd0c398 Clear the wsg_grabber test modules: python_pkg is now under the cap
The last six over-cap files split into eleven new modules, in two passes
since the first cut left five still over. python_pkg has no violations
left at all, down from 50 when this effort started.

Each split carries the whole preamble including the module's pytest
fixtures - started, toolkit and session are taken as parameters and
resolve lexically, so a hand-written import block would have missed
them. ruff then pruned 82 unused imports.

Pass count is 277 before and after, checked after both passes.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
2026-08-16 18:19:26 +02:00
b60cb4972a Clear five python_pkg packages under the cap
android_ui, app_icons, token_audit, focus_policy and random_jpg are now
entirely under 250 lines. Only wsg_grabber's six test modules remain in
python_pkg.

android_ui/driver.py 411 -> 205 took two cuts and a class split: the
element and error types plus the XML parser to _elements.py, and the
text-entry methods to a TextEntryMixin, using the ABC-with-abstract-
methods shape because the tests call them as bound attributes.
app_icons/glyphs.py 291 -> 93 by moving the twelve path fragments out.

Three intermediate failures, all module-level globals that travelled
with their code (_logger, re, _SAME_FIELD_X_TOLERANCE_PX) - the same
class of miss as _make_verifier, which ruff cannot flag.

Counts unchanged: 81, 137, 14, 44, 57.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
2026-08-16 18:14:36 +02:00
e3214c813c Clear the seven over-cap code_tutor test modules
Some checks are pending
Pre-commit checks / pre-commit (push) Waiting to run
Python tests / test (push) Waiting to run
Shell tests / shell-tests (push) Waiting to run
Shell tests / arch-tests (push) Waiting to run
Partitioned at the section banners each file already used, into ten new
modules named for what they cover. python_pkg/code_tutor is now entirely
under the cap, from thirteen violations.

The splitter copies the whole original header into each new file and
lets ruff's F401 pass prune it - 59 imports removed correctly, versus a
hand-written import block failing on every earlier test split today.
That does not cover module-level helpers: _make_verifier sat between two
banners and was left behind by its callers, so it moved to conftest.py,
taking test_verifier.py's shadowing copy of _make_live_mock with it.

No patch string changed. Pass count is 217 before and after.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
2026-08-16 18:06:38 +02:00
1d1938ce23 Split code_tutor cli and verifier, deleting two lint suppressions
cli.py 345 -> 222 and _verifier.py 347 -> 228, via _cli_checks.py and
_verifier_support.py.

The cli seam cut straight through code covered by its per-file-ignores.
Rather than copy S607 and PERF203 onto a second entry, both are fixed:
systemctl is resolved with shutil.which, and the readiness poll no
longer carries a try/except in its body now that the probe is its own
function. Both ignores are deleted from meta/pyproject.toml and ruff
passes without them - the repo has two fewer suppressions than before.

The which() fix adds a systemctl-is-None branch, so the pass count goes
216 -> 217: one new test for it, no existing test touched. Coverage is
back at 100% by covering the branch, not excluding it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
2026-08-16 17:57:38 +02:00
c113a3a192 Split the code_tutor import graph and scan tables out of _analyzer
_analyzer.py 388 -> 226. The file walk, dotted-name map, import
resolution and fingerprint go to _deps.py; the skip frozensets and the
function regex to _scan_tables.py, which covered the last nine lines
after the first cut landed at 259.

_is_binary is the only name any test patches on _analyzer and it stays
put, so no patch string needed editing. Only test_analyzer.py's import
block had to be split.

Pass count is 216 before and after.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
2026-08-16 17:48:32 +02:00
277cb9fd6f Split the code_tutor challenge flows out of _challenge
_challenge.py 467 -> 236. The two flows and run_coding_challenge go to
_challenge_flows.py; the three static prompt strings to
_challenge_prompts.py, which covered the last eleven lines after the
first cut landed at 261.

The 13 patch strings in test_challenge_part3.py were repointed per test,
by which function is under test - the _run_user_impl tests keep
_challenge. Getting that wrong does not fail here, it HANGS: an
unintercepted _collect_and_rate_tests ran the real interactive flow and
spun the suite for two minutes. Runs are now under an explicit timeout,
and the patch targets are asserted to resolve before pytest starts.

Pass count is 216 before and after.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
2026-08-16 17:42:50 +02:00
40a4eaf5b5 Split the code_tutor pytest runner and verdict streaming out
_challenge_support.py 402 -> 237. _pytest_clean and _patch_and_test go
to _pytest_runner.py with the subprocess/os/sys imports they solely used;
_stream_verdict and _parse_verdict go to _verdict.py with rich's Live.

code_tutor patches by absolute string rather than a MOD constant, so the
seam cost 15 per-name rewrites instead of one sed. Two intermediate
failures caught by running it: the test modules import the moved names
directly, and five patch strings still named the old module.

Pass count is 216 before and after.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
2026-08-16 17:33:58 +02:00
2c01a0267e Clear the last three brother_printer test modules over the cap
Some checks are pending
Pre-commit checks / pre-commit (push) Waiting to run
Python tests / test (push) Waiting to run
Shell tests / shell-tests (push) Waiting to run
Shell tests / arch-tests (push) Waiting to run
test_consumables_part2.py 273 -> 133 (renamed test_counter_scales.py),
test_consumables.py 272 -> 123, test_check_brother_printer.py 275 -> 112.
python_pkg/brother_printer is now entirely under the 250-line cap, from
fifteen violations at the start.

The consumables split lands exactly on the MOD/MOD_PD boundary an earlier
commit introduced, so the two-constant workaround dissolves instead of
being carried forward - each file has one module under test again.

Pass count is 402, unchanged across all eight commits of this pass.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
2026-08-16 17:27:07 +02:00
74eee2b57f Split the brother_printer display and usb_query test modules
test_display.py 363 -> 204, with the supply maths and its display going
to test_supply_levels.py. test_usb_query.py 328 -> 126, with the retry
and sequencing tests going to test_usb_query_sequence.py.

That removed the last reader of the six _supply names through display,
so display.py's __all__ shrinks to the two public entry points and the
stale comment about tests reaching them there is gone. Checked by grep
across python_pkg and an import-time attribute probe, not by the suite.

The first run after the usb_query split reported 393: TestParsePagecount
sat between the two moved classes and travelled with them. Fixed.

Pass count is 402 before and after.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
2026-08-16 17:23:47 +02:00
607ba98c85 Split the brother_printer cups_service test modules under the cap
test_cups_service.py 390 -> 172 (usblp port status to _port),
part2 261 -> 115 (status mapping and discovery out), part3 260 -> 105
(the four healthy-port cases out). part3 was one 235-line class, so it
split by method rather than by class.

The four part-numbered files are renamed for what they cover; three of
them now name _cups_fallback, matching where the code moved earlier.

The first run after the part2 split reported 400, not 402: the moved
classes needed subprocess and get_status_info that my hand-written
import block had dropped. Fixed, and the count is 402 again.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
2026-08-16 17:19:55 +02:00
65bc65c7e3 Split the brother_printer cups_queue test modules under the cap
test_cups_queue.py 458 -> 167, partitioned at class boundaries into
_actions (the four shell-out primitives) and _backend (health check and
error-log scanning). test_cups_queue_part2.py 278 -> 131, with the four
_handle_* dispatchers moving to _handlers.

No MOD changed: unlike the source splits, every patched name already
lives in the module each file names, so this is a pure partition. The
two part-numbered survivors are renamed for what they cover.

Pass count is 402 before and after.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
2026-08-16 17:15:55 +02:00
a438652aaa Split brother_printer dropped-page detection out of consumables
consumables.py 279 -> 217. check_page_delivery and _snapshot_counters
move to _page_delivery.py.

The state-file primitives stay put: test_consumables.py patches
_load_consumable_state, _save_consumable_state and CONSUMABLE_STATE_FILE
on consumables, and moving them would have let a test write to the real
~/.config/brother_printer/state.json. Verified by mtime after the run,
not just by a green suite.

test_consumables_part2.py patches the same three primitives from both
sides of the seam, so it gains a second MOD_PD applied per class rather
than a blanket swap. Pass count is 402 before and after.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
2026-08-16 17:07:21 +02:00
143dd9ff3d Split brother_printer CUPS fallback query out of cups_service
cups_service.py 406 -> 234. The IPP attribute queries, the CUPS
status-code mapping, find_cups_printer_name and query_usb_via_cups move
to _cups_fallback.py; cups_service keeps service control and raw USB
port status.

The seam is not the section header: test_cups_service_part2.py imports
_get_cups_economode and find_cups_printer_name together and patches
shutil once, so those two had to travel as a pair. test_cups_service.py,
the largest module, needs no edit.

usb_query.py reaches the fallback through a late import_module, so both
that string and the absolute patch target in test_usb_query.py moved
with it. Pass count is 402 before and after.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
2026-08-16 17:02:42 +02:00
cf44e54215 Split brother_printer queue display and fix prompt out of cups_queue
cups_queue.py 424 -> 245. display_cups_queue_status, _offer_queue_fix,
the _dwj_* choices and the _handle_* dispatchers move to _queue_fix.py.

The _cups_* action primitives stay put: test_cups_queue.py patches
subprocess and time on cups_queue, and those primitives are their only
consumers, so moving them would have left subprocess.run unpatched and
free to restart the real CUPS service mid-suite. test_cups_queue.py
therefore needs no edit; only part2/part3 repoint their MOD.

Pass count is 402 before and after.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
2026-08-16 16:53:51 +02:00
e03fbd17e9 Split wsg_grabber/tests/test_review.py under the 250-line cap
Split along the same seams the source took: test_review_render.py covers what
now lives in review._render, test_review_undo.py covers review._undo.

The two fixture builders they share moved into the existing conftest.py rather
than a _helpers.py -- the name-tests-test hook requires everything else under
tests/ to be named test_*.py, and syncyomi_guard/tests/conftest.py is the
precedent for plain shared builders living there. Sharing beats copying: jscpd
fails the commit above 2% duplication and this repo already carries four partN
test files.

277 passed, the same count as before, so nothing was dropped. Two intermediate
runs caught real NameErrors from imports the extracted module needed.

test_review.py 297 -> 112, test_review_render.py 102, test_review_undo.py 79.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
2026-08-16 16:24:48 +02:00
324ec0c9b5 Split wsg_grabber/downloader.py under the 250-line cap
Worker did two separable jobs. Both become mixins, because the tests drive
their methods as bound attributes (worker._download_one(), worker._fetch_thread)
and free functions would have broken that:

  _scan.py    ScanMixin      -- threads.json, the archive, one changed thread
  _fetch.py   DownloadMixin  -- claim a file, fetch it, record the outcome

_worker_base.py holds WorkerBase(ABC) with the state both halves touch and the
three methods Worker supplies. Two earlier shapes were abandoned rather than
suppressed: NotImplementedError stubs are uncovered lines under the 100% gate,
and `if TYPE_CHECKING` stubs satisfy mypy but leave pylint reporting no-member.
The ABC satisfies both with no disable comment anywhere.

_downloaded needs a real class-level default: _fetch does `self._downloaded += 1`
and pylint only accepts an attribute as assignable when there is something to
assign to. Worker.__init__ still owns the instance value.

downloader.py 420 -> 238, _scan.py 134, _fetch.py 98, _worker_base.py 52.
277 tests pass.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
2026-08-16 16:01:11 +02:00