{ "intent": "Bring phone_backup.sh under the repo's 250-line cap by moving its five capture functions into a sourced library, and cover that library at 100% — it had no test at all before.", "scope": [ "phone_focus_mode/phone_backup.sh - 333 lines down to 229", "phone_focus_mode/lib/backup_capture.sh - new: dump_packages, pull_apks, pull_sdcard, dump_settings, dump_focus_state", "phone_focus_mode/lib/tests/backup_capture_harness.sh - new: the fake device", "phone_focus_mode/lib/tests/test_backup_capture.sh - new: 22 assertions", "Non-goal: any change to deploy.sh. phone_backup.sh is PC-side and appears in neither deploy list, so this split needs no list edits.", "Non-goal: touching verify_backup, write_manifest, print_manual_checklist or main, which stay in the entry script." ], "changes": [ "Moved the five capture functions with meta/scripts/extract_shell_functions.py rather than by line range. The tool placed the source line before SCRIPT_DIR was defined, so it was moved by hand to sit after adb_common.sh, which defines the helpers the library calls.", "Moved SDCARD_DIRS into the library with pull_sdcard, its only reader. Left in the entry script it would be an assignment that file never reads (SC2034), and the pre-commit hook runs shellcheck without -x so each file must stand alone.", "Rewrote three constructs that no test could cover, the same kcov artifact found during the monitor backfill: the SDCARD_DIRS multi-line literal, and two multi-line `mapfile < <(...)` process substitutions. kcov instruments the continuation lines but bash only reports a statement at its first line.", "Rewrote dump_focus_state's `{ ... } >file` group as a single printf. The group's closing brace is an instrumented line bash never reports; rewriting it as individual redirects instead drew SC2129, so one printf statement is what satisfies both without a suppression.", "Added a fake device mocking adb_cmd, the single boundary all five functions cross, so adb_common.sh's device-detection surface stays out of the test." ], "verification": [ { "command": "bash phone_focus_mode/lib/tests/test_backup_capture.sh", "result": "pass", "evidence": "Results: 22 passed, 0 failed. There was no test for this code before." }, { "command": "bash meta/scripts/shell_coverage.sh phone_focus_mode/lib/tests/test_backup_capture.sh backup_capture.sh", "result": "pass", "evidence": "backup_capture.sh: 52/52 lines = 100.00%, exit 0 at the default 100% minimum. First measured run was 79.17%." }, { "command": "mutation run: 13 mutations of the capture behaviour, each required to fail the suite", "result": "pass", "evidence": "13 killed, 0 survived. Includes reintroducing the stdin-draining bug pull_apks' own comment documents (mapfile replaced by read), which the suite catches. The first run had one survivor - the keepass mkdir was masked because the mock's pull created the directory too - now asserted with pulls forced to fail." }, { "command": "bash meta/scripts/verify_shell_split.sh 0f210e5 phone_focus_mode/phone_backup.sh phone_focus_mode/phone_backup.sh phone_focus_mode/lib/backup_capture.sh", "result": "pass", "evidence": "Nine of the ten top-level functions hash identical before and after. dump_focus_state is reported as differing, which is correct and deliberate: it is the one function rewritten for measurability. Its output was checked separately." }, { "command": "diff of focus-state.md rendered by HEAD's dump_focus_state against the rewritten one, same inputs", "result": "pass", "evidence": "IDENTICAL - byte-for-byte, so the rewrite is behaviour-preserving despite the hash difference above." }, { "command": "shellcheck on each of the four touched files individually (no -x)", "result": "pass", "evidence": "All four clean standalone. SC2129 and SC2318 were both raised during this work and both fixed structurally; SC2318 was a real bug, ${dir} referenced in the same local that declared it, caught before commit." }, { "command": "timeout 30 bash phone_focus_mode/phone_backup.sh --help; and --verify against an empty directory", "result": "pass", "evidence": "--help exits 0, proving the source chain resolves. --verify exits 1 on an empty directory, naming the four missing files; a worktree at HEAD exits 1 with the same output, so the behaviour is unchanged." }, { "command": "bash meta/scripts/check_file_length.sh --all", "result": "pass", "evidence": "21 over-cap files, down from 22. phone_backup.sh is 229, backup_capture.sh 52 excluding comments (114 with), the harness 107 and the test 162 - all under the cap." } ], "risks": [ "dump_focus_state is the one function not moved verbatim. Its rendered output is proven byte-identical for the inputs tested, but it now reads packages-removed-for-user0.txt into a variable before writing, so a file large enough to matter is held in memory where it was streamed before. Backup package lists are a few hundred lines, so this is not a practical concern.", "The library is verified against a stub device, never a real phone. The mock encodes what pm/df/settings are believed to return; a device whose output differs in shape would still be mis-parsed.", "phone_backup.sh now sources a second library at startup. If lib/backup_capture.sh is missing from a deployment the script fails immediately rather than at first use — louder, but a new failure mode." ], "rollback": [ "git revert the commit. phone_backup.sh returns to a single 333-line file, over the cap and untested.", "After rollback, run timeout 30 bash phone_focus_mode/phone_backup.sh --help expecting exit 0, and --verify against an empty directory expecting exit 1.", "Nothing else sources lib/backup_capture.sh, so the revert is self-contained." ] }