Commit Graph

16 Commits

Author SHA1 Message Date
b83ce5d3c7 Fail the build on any shellcheck finding; fix the 222 the old gate hid
Some checks failed
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
billsplit CI / quality (push) Has been cancelled
reverse-survivors CI / check (push) Has been cancelled
The shellcheck hook ran with --severity=warning, so info- and style-level
findings never failed a commit. 222 had accumulated across 94 of 244 shell
files. They were not all style noise -- six were bugs:

  - Five scripts in single_use/misc/testsAndMisc-bash/ sourced
    $SCRIPT_DIR/../../lib/common.sh, one directory too shallow. Nothing exists
    at that path, so all five failed at runtime.
  - setup_personal_website.sh and setup_kcd2_dice_solver.sh called usage()
    (which ends in `exit 0`) and then `exit 1` for an unknown command. The
    exit 1 was unreachable, so a typo'd subcommand reported SUCCESS.
  - Six i3blocks scripts had a `# shellcheck source=` directive missing the
    scripts/periodic_background/ path segment. The stale path had silently
    disabled source-following for all of them.
  - diagnose_arch_performance.sh word-split a newline-joined file list; when
    the find came back empty, awk got no file operands, fell through to
    reading stdin, and the diagnostic hung.
  - lib/common.sh's handle_arg_help_or_unknown ended in an unreachable
    `return 0` while documenting "Returns: 0 if handled" -- a contract no code
    path could satisfy.
  - deploy.sh's SCRIPT_DIR is reassigned by config.sh, which it sources at top
    level, and config.sh can repoint it at the on-device path
    /data/local/tmp/focus_mode. Renamed to DEPLOY_DIR.

The SC1091 bulk (81 findings) was resolved rather than silenced: .shellcheckrc
sets external-sources + source-path=SCRIPTDIR so sourced files are actually
followed and analysed. That is what surfaced the i3blocks and deploy.sh bugs.

17 `# shellcheck disable=` directives remain, each with a written reason, and
only where the finding is provably wrong: injection-test payloads that must not
expand, literal source text being grepped for, POSIX sh scripts on the phone
where arrays do not exist, and source paths that only exist at runtime.
Everything else was fixed in code.

Verified in a CLEAN worktree, not just my own tree. config.sh sources the
gitignored config_secrets.sh, which exists locally but not in a fresh checkout;
a working-tree-only check passed while ci_mirror.sh -- which lints HEAD in a
clean worktree, exactly as CI does -- failed on it.

The gate is verified to bite: a file whose only finding is SC2016 (info) fails
the hook, while the old --severity=warning gate exits 0 on it.

third_party/ is excluded -- vendored upstream, matching the prettier hook.

Not fixed, reported: check_polling_antipatterns.sh carries a pre-existing
SC2034 disable that hides a real bug -- its `patterns` array is never used, so
the checker detects nothing. Confirmed unchanged by this commit.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-03 19:49:11 +02:00
79829ec8a2 Diagnose the pacman first-hook stall; fix the drift that hid a stale wrapper
pacman intermittently freezes at its FIRST PreTransaction hook - 5 abandoned
transactions since 2026-07-29, stalls of 4 minutes to overnight, always fine on
retry. This investigates it and repairs the real defects found on the way.

The stall is NOT fixed, and deliberately so: no verified defect was found, so
nothing pacman-side was changed.

  - Not guard-lib. /etc/guard-lib/targets was empty, so that hook script
    provably exits in milliseconds. The failures share POSITION (first hook of
    the transaction), not identity: one stall hit the hosts hook back when that
    one ran first.
  - Not reproducible on demand. 65 real transactions (40 idle, 25 under 2.6 GB
    of memory pressure) produced zero stalls. Against the historical 4-in-56
    rate that is ~0.9% likely, so the trigger is conditional on something a
    synthetic load does not recreate. Memory pressure is untested at the load
    that actually occurred - not refuted.

So diagnose_pacman_hook_stall.sh drives real transactions with a watchdog, and
its --watch mode passively captures /proc/<pid>/{stack,wchan,syscall}, the
process tree, meminfo and PSI from the next REAL stall. No pacman-side fix
should ship before that dump exists.

Three real defects were found and fixed:

1. The deployed pacman wrapper was 19916 B against a 27510 B source, never
   sourcing the shared pacman_lock_lib.sh that makepkg_wrapper already used.
   It hid because check_pacman_wrapper() only tested that files EXIST. Both
   installers now write a drift manifest covering source AND installed hashes;
   the checker replays it with sha256sum -c and fails closed when it cannot
   verify. Manifest paths are absolute - relative ones resolve to nothing under
   systemd's cwd=/ and would have looped reinstalling forever.

2. The guard-lib migration (66c4698) was never finished here: guard-lib was
   installed with zero registered targets while the legacy hosts-guard layer did
   all the work, so every transaction ran two PreTransaction hooks and the first
   one did nothing. migrate_hosts_guard_to_guard_lib.sh registers hosts,
   nsswitch and resolved, retires the legacy hooks and units, and has a
   --rollback that was actually exercised. Plugins install to a stable path
   because guardctl records absolute plugin paths. Side effect: one hook pair
   instead of two, transactions ~5-6s -> ~2-3s.

3. "One heavy job at a time" was a rule a human had to remember. heavy_job_lock.sh
   makes the overlap impossible: one system-wide flock shared between root and
   the user (a per-user lock would give root's pacman and the user's builds
   separate locks and no exclusion at all). Wired into pacman TRANSACTIONS only
   - read-only queries stay instant - plus makepkg_capped and the coverage hook.
   Always fails open.

Two bugs surfaced only by running --rollback, both of which briefly left
/etc/hosts with no immutable flag: fg_uninstall clears +i on its way out while
legacy watchers only fire on PathModified, and `((i > 20)) && break` left the
loop body's status at 1, which under set -e aborted the rollback silently - but
only when the loop actually ran, so --dry-run looked healthy.

Not migrated: the shutdown-schedule guard (/etc/shutdown-schedule.conf does not
exist here). Not wired: Flutter/Gradle builds, which have no single entry point.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-03 15:15:34 +02:00
4d9f486176 billsplit: build a real APK by dropping file_picker for file_selector
billsplit had never produced an Android APK. file_picker 11.0.3 — the latest
release on pub.dev — is wedged under AGP 9 in both directions: it skips
applying the Kotlin Gradle Plugin when AGP >= 9, so with built-in Kotlin off
its own Kotlin never compiles (`cannot find symbol: FilePickerPlugin`), and
with built-in Kotlin on its buildscript leaks KGP onto the global classpath
via rootProject.allprojects, which AGP then rejects for every module.

The app's Gradle config was never at fault: android/app/build.gradle.kts and
settings.gradle.kts are byte-identical to habit_stack, home_inventory and
diet_guard_app, which all build. Only the dependency differed, so the fix is
the dependency — the toolchain stays on the house-standard AGP 9.

Ported both call sites to the official file_selector. Its save flow returns a
destination and leaves the write to the caller, where file_picker wrote the
bytes inside the plugin; that write now goes through an injectable seam
because a real dart:io future never completes inside testWidgets' fake-async
zone, so a widget test could otherwise never observe the resulting snackbar.
The test double moves from a MethodChannel mock to a FileSelectorPlatform fake
(file_selector speaks Pigeon, so a channel mock would not intercept it), and a
plain non-widget test covers the real on-disk writer to hold 100% coverage
without an ignore.

The launcher icon is now verified from the built artifact: the ic_launcher
assets unzipped from the APK render as the charcoal + gold perforated receipt,
not the stock Flutter beach. Peak build footprint is 1.86 GiB, inside budget.

Also rewrites ci_mirror.sh to run its gates in a disposable worktree at HEAD.
`pre-commit run --all-files` stashes the live tree, and in a shared checkout
that made an unrelated agent's uncommitted work collide with hook auto-fixes,
failing the push while blaming an innocent hook — and putting that work inside
a stash window, which memories/git-rules.md forbids. A clean worktree needs no
stash and is a truer mirror of CI, which never sees a dirty file.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-03 14:15:40 +02:00
c600760e71 Move billsplit Flutter app into the monorepo
billsplit was a standalone Flutter app in ~/Downloads with one commit and no
git remote — that directory was the only copy of its history.

Three things the move needed beyond copying the tree:

- The root .gitignore silently swallowed 32 of its files (*.png, *.ico,
  *.manifest). Ignored files do not error on `git add`, they just vanish, so
  this would have committed an app that cannot build. Added a trailing block
  of ! overrides plus .binary-allowlist (which did not exist yet) — the
  documented in-repo exception for binaries a build genuinely needs. The
  ../testsAndMisc_binaries/ symlink scheme does not apply: those directories
  are gone, and a symlinked PNG breaks a fresh clone.

- billsplit/.github/workflows/ci.yaml was inert once nested; GitHub only reads
  workflows at the repository root. Relocated with a working-directory and a
  billsplit/** path filter.

- The app's coverage gate moved to python_pkg/billsplit_coverage/ so every
  Python file stays under python_pkg/, and gained a test suite at 100% branch
  coverage as that location requires. billsplit/ now holds only Dart.

Also replaces the stock Flutter icon with a family icon from the shared
generator. The glyph is a filled silhouette because the family's 72px stroke
cannot carry a fine torn edge, and the split is an evenodd-cut perforation so
the holes stay transparent on the foreground and monochrome layers.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-03 12:22:22 +02:00
2b3c1a69f7 fix(hooks): make git hooks and their toolchain mandatory and fail-closed
A completely absent `prettier` binary went unnoticed long enough for
unformatted markdown to reach main. Two independent causes, both fixed here.

No pre-push hook existed. The three hooks declaring `stages: [pre-push]` in
.pre-commit-config.yaml — prettier, ci-mirror, pytest-coverage — therefore
never ran locally at all. Adds one.

The pre-commit hook failed open. shfmt and shellcheck were wrapped in
`if command -v`, and the entire pre-commit framework was skipped with a
warning when its binary was missing. A silently skipped gate is
indistinguishable from a passing one, so a missing tool now aborts.

- lib/common.sh: require_tool tries `sudo -n pacman -S` and aborts with the
  exact command when a tool cannot be installed non-interactively;
  verify_toolchain applies it to all eight binaries the hooks invoke.
- The pre-push hook resolves git's stdin refs into --from-ref/--to-ref.
  Without a range, `pre-commit run --hook-stage pre-push` checks *staged*
  files, and nothing is staged at push time, so prettier would have received
  zero files and passed vacuously.
- ensure_hooks_path repairs core.hooksPath drift on every hook run.
- meta/scripts/install_hooks.sh covers the one case the hooks cannot
  self-heal — a fresh clone where the config was never set, so nothing runs.
  `--check` reports state and exits non-zero when incomplete.

Note `pre-commit install` must never be used here: it writes into
.git/hooks/, which git ignores entirely while core.hooksPath is set.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-03 11:09:11 +02:00
7f706adb11 Make the hook tests' non-vacuity re-verifiable, and gate it
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
63a5124 claimed "12 tests, 12 killed by at least one mutation", but the harness
that proved it lived in a session scratchpad. That left an unfalsifiable claim
about tests whose entire purpose is to not be vacuous -- and the facts it
encodes are exactly the ones a future edit to either hook would invalidate.

The mutations are per guard, not one whole-body gutting. Gutting a hook body
kills only the three positive tests, because a gutted hook emits neither a
launch nor any output, which is precisely what the nine negative tests expect.
Run that way the negative tests look vacuous when they are not; dropping each
guard in turn is what actually exercises them.

Two failure modes of the harness itself are handled. A mutation that leaves the
hook text unchanged now raises instead of quietly reporting "killed 0": a
behaviour-preserving edit to a hook, such as renaming a variable, would
otherwise turn a mutation into a no-op and weaken the proof without a word.
Verified by renaming count to n in a copy of the start hook -- the copy still
printed the correct banner, and the tool still refused it. And where the hooks
are not deployed at all the tool exits 0 rather than failing, since a CI runner
has no ~/.claude to mutate.

Wired into pre-commit scoped to the suite and the mutation definitions, the only
two files that can invalidate the proof. It costs ~40s, so it stays off
unrelated commits. It gates rather than warns: survivors mean exit 1.

One finding stands recorded: start-no-readable-guard kills nothing on its own.
The start hook's [[ ! -r "$STATE" ]] guard is redundant, since jq's stderr is
suppressed and an empty count already fails the arithmetic, so only the compound
mutation reaches those cases. The guard is left in place as defence in depth.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-25 17:37:34 +02:00
1b7a364e64 requirements-sync: map bs4 and exempt foreign-runtime imports
Some checks are pending
Pre-commit checks / pre-commit (push) Waiting to run
Python tests / test (push) Waiting to run
Two fixes the checker needs for archwiki_rag, left uncommitted in the
working tree and therefore stashed away by pre-commit on every push:

- bs4 resolves to the beautifulsoup4 distribution; without the mapping
  the checker reports it as undeclared.
- mcp_server is resolved by the knowledge-rag uv tool venv at runtime,
  not by CI's `pip install -r requirements.txt`. Declaring it would be
  wrong, so it is exempted via _FOREIGN_RUNTIME_IMPORTS with a comment
  naming the owning venv.

Not authored in this session; committed because the pre-push CI mirror
cannot pass while these live only in the working tree.
2026-07-19 18:58:35 +02:00
20bc1aeb02 linux_configuration: self-hosted dufs cloud storage + KeePass WebDAV auto-sync
Add setup_dufs_cloud.sh: a self-sustaining installer that stands up dufs
(single Rust binary; web UI + built-in WebDAV) on 127.0.0.1:5000 behind the
existing Caddy + DuckDNS exposure stack at a new subdomain, so files are
reachable from mobile network over HTTPS. An event-driven systemd path unit
mirrors ~/Keepass/Passwords.kdbx into the served folder on every change, so
KeePassDX syncs the vault natively over WebDAV.

Paranoid secret handling for the gitignored .dufs_cloud.conf (plaintext web
password): ignored at repo-root + linux_configuration + **/ globs; a new
check-no-dufs-conf pre-commit hook blocks it even against `git add -f`; the
installer refuses to write it unless git check-ignore confirms it is ignored;
the password is hashed via `openssl passwd -6 -stdin` (never in argv); and a
cloud-root guard forbids serving home/.ssh/.config/.gnupg/the repo/root.

Verified: dufs auth (401/200) + WebDAV (207) against the real v0.46.0 binary,
idempotent Caddy/DuckDNS/unit-render logic, and a live phone-on-cellular test.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0126QvdMrv2WNeUVShFiLTc3
2026-07-12 03:40:54 +02:00
deada49399 Add CI-mirror pre-push gate + requirements-sync check (monorepo variant)
Some checks are pending
Pre-commit checks / pre-commit (push) Waiting to run
Completes the repo-wide safeguard rollout (see screen-locker). Adapted for
the python_pkg monorepo:
- meta/scripts/check_requirements_sync.py: fails if a python_pkg import
  isn't declared in requirements.txt. Treats python_pkg subpackages
  (imported bare) as first-party and maps PIL->pillow (both were false
  positives here — pillow is already declared).
- meta/scripts/ci_mirror.sh: pre-push gate = requirements.txt-only clean
  venv (hash-gated), `SKIP=pytest-coverage pre-commit run --all-files`,
  then the OOM-safe changed-packages runner inside that venv (user chose
  changed-packages over the full raw suite to stay within memory).
- Existing pre-commit-stage pytest-coverage hook left untouched.

Verified: mirror green end-to-end (clean venv + all-files + pytest).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018PMRRVVkeRyrSaaqueMZKG
2026-07-11 18:06:17 +02:00
d8f8605275 Drop -n 4 xdist parallelism from the pytest-coverage pre-commit hook
The coverage gate failed twice in a row on GitHub Actions on the exact
same line (wake_alarm/_alarm_display.py:71), with the run rerun on the
identical commit -- ruling out a one-off flake. Could not reproduce
locally across 6 attempts, including running the exact same -n 4
invocation directly. The likely cause is a pytest-xdist/pytest-cov
worker coverage-combine timing issue specific to the CI runner that
isn't practical to root-cause further from here. Running the hook
serially removes the risk outright; local timing shows no meaningful
slowdown.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A7vbgtFfZmfxJtN5DdtJky
2026-06-22 08:29:54 +02:00
038e08d2be feat: split oversized modules for 500-line limit, fix kasa coverage gap
Split diet_guard/_gatelock.py, wake_alarm/_alarm.py, and the
usage_report.py/_usage_report_parsing.py pair into focused
sub-modules so every Python file is <= 500 lines, satisfying
test_file_length.py. Install python-kasa into .venv (declared in
requirements but missing after the 3.13->3.14 venv upgrade),
fixing 8 failing smart_plug tests and restoring 100% coverage.

Also includes prior in-progress work from the working tree: the
wake_alarm Progress/View/Hardware field-grouping refactor,
brother_printer query module + tests, diet_guard foodbank/state/cli
updates, new shared coerce/logging_setup helpers, morning_routine
orchestrator tweaks, dwm window-manager config, gaming scripts, and
misc maintenance/digital-wellbeing script updates.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-14 07:19:37 +02:00
2e1e370c0f refactor: extract all inline Python from shell scripts into proper .py files
Move every multi-line python heredoc/`-c` block into a dedicated .py file so
ruff, mypy, pylint, bandit, and pytest can apply to it:
- linux_configuration/zsh/calc-live.zsh → python_pkg/live_calc/calc_eval.py (100% branch cov, 46 tests)
- meta/scripts/check_ai_evidence.sh → meta/scripts/validate_evidence.py
- meta/scripts/check_agent_contract.sh → meta/scripts/validate_contract.py
- phone_focus_mode/lib/monitor.sh → phone_focus_mode/lib/monitor_report.py
- phone_focus_mode/deploy.sh → phone_focus_mode/strip_workout_hosts.py
- linux_configuration/.../analyze_repo.sh → fast_count.py

Also: add zsh-syntax pre-commit hook (zsh -n); exclude zsh from shellcheck;
add tests for all 4 non-python_pkg helpers; update CLAUDE.md Shell Style with
the no-inline-Python rule.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-06 10:31:48 +02:00
20d5d1f89b fix(usage_report): stop charging atop's HZ field as CPU; bundle since-last-report mode
atop's `-P PRC` output inserts the clock-tick rate (HZ=100) between the
`state` and `utime` columns. Both the Python parser and the native C
aggregator read that constant as utime for every record, charging a flat
1 CPU-second per record — so cpu_seconds collapsed to pid_count and
short-lived fork-storm commands (xset, dd, chronyc) topped the CPU table
(xset showed 67h). The old test fixtures lacked the HZ field, so code and
tests agreed on the bug.

- _parse_prc / atop_agg.c: read utime/stime past the HZ field (after+2/+3,
  tokens[10]/[11]); bump the length guards accordingly
- restore C/atop_agg (deleted in 89b4f59) under linux_configuration/C/,
  where the build path resolves; corrected test fixtures to include HZ
- _atop_agg_binary: fall back to the Python parser when the C source tree
  is gone instead of trusting an orphaned cached binary
- add regression tests proving HZ is not summed as CPU
- bundle the in-progress since-last-report multi-day aggregation (segments,
  -b/-e bounding, persisted state, window merging) and its tests/conftest
- meta: gate linux_configuration/tests in pytest_changed_packages.py

Verified by running usage_report.py --date 20260604: Top CPU now led by
SkyrimSE; xset/dd/chronyc fall to ~0. C unit tests + full pytest suite green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 18:13:47 +02:00
07792e75d2 refactor: split wake_alarm modules, fix ruff violations, enforce global coverage
- Split _alarm.py (1059 lines) into _alarm.py + _audio.py + _challenges.py
- Split test files (1305 / 725 lines) into 6 files, all under 500 lines
- Replace random.* with secrets.* (S311); fix RUF001, SIM117, E501 ruff errors
- Rewrite pytest_changed_packages.py to always run all packages with global
  --cov python_pkg coverage (100% branch coverage enforced across whole tree)
- Add DISMISS_ROUNDS_REQUIRED=2 and DISMISS_FLASH_SECONDS=4 to _constants.py

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-30 22:13:32 +02:00
0c1e395008 Split modules, fix tests, fix pre-commit batching
- steam_backlog_enforcer: extract _hltb_search.py and _scanning_confidence.py;
  split oversized test files into *_part2/3/4.py
- screen_locker: extract _early_bird.py and _window_setup.py from screen_lock.py;
  fix patch targets in tests (screen_lock.* -> _window_setup.*)
- wake_alarm: use shutil.which('xset') to avoid S607; add TestDisplayHelpers tests
- linux_configuration/usage_report: split into _parsing.py and _types.py;
  add bin/__init__.py (INP001); fix RUF002 (× -> x)
- pre-commit: add require_serial: true to pytest-coverage hook to prevent
  file batching across 24 CPU cores (was causing 12 parallel partial-coverage runs)
2026-05-22 22:48:28 +02:00
db6276b3ff refactor(linux_configuration): move remaining dirs + scripts/ to meta/
- Move fresh-install/ → scripts/single_use/fresh-install/
- Move hosts/ → scripts/periodic_background/hosts/
- Move i3-configuration/ → scripts/periodic_background/i3-configuration/
- Delete linux_configuration/LaTeX/, nix-poc/, report/ (dead dirs)
- Move repo-root scripts/ → meta/scripts/
- Update root .pre-commit-config.yaml: scripts/ → meta/scripts/ (9 entries)
- Update run.sh ARTIFACT_INIT_SCRIPT to meta/scripts/
- Update fresh-install/main.sh: hosts/install.sh + i3-configuration/install.sh paths
- Update check_python_location.sh: add meta/scripts/ to exception list
- Fix midnight flakiness in test_recent_workout_returns_true: use timezone-aware
  local noon instead of now-1h to avoid SQL date() boundary issues
2026-05-15 00:53:01 +02:00