From 8bcdd20915cb6b59f4243abf46e68800e8b9a0b5 Mon Sep 17 00:00:00 2001 From: Krzysztof kuhy Rudnicki Date: Fri, 9 Oct 2026 18:46:57 +0200 Subject: [PATCH] endurain_import: phone fallback skips files already imported, one summary line RunnerUp keeps every export on the phone, so each run re-pulled the whole history into the inbox and re-triggered every inotify watcher there. pull_from_phone now skips names already in the inbox or in processed/ (processed_dir_for, shared with the importer) and logs one summary line, escalated to WARNING when any pull failed. Also: prune evidence/contracts to newest 20 (repo-size cap). Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_018RHwVPyiiQMPh4M3JQwQku --- .../archive-billsplit-2026-08-23.json | 21 --- ...-bucket-catch-poker-stakes-2026-08-23.json | 20 --- ...ci-mirror-scoped-push-gate-2026-08-23.json | 21 --- .../endurain-db-dedupe-2026-08-23.json | 19 --- ...port-dedupe-and-adb-serial-2026-08-23.json | 22 --- .../endurain-runnerup-webdav-2026-08-23.json | 20 --- .../extract-android-guardian-2026-08-23.json | 20 --- .../extract-digital-wellbeing-2026-08-23.json | 22 --- .../extract-hosts-blocker-2026-08-23.json | 24 ---- .../extract-mtk-root-2026-08-23.json | 20 --- .../extract-phone-focus-mode-2026-08-23.json | 21 --- ...extract-system-maintenance-2026-08-23.json | 20 --- .../extract-three-apps-2026-08-23.json | 20 --- .../flatten-i3-and-misc-2026-08-23.json | 19 --- ...latten-linux-configuration-2026-08-23.json | 21 --- .../hosts-guard-tests-2026-08-23.json | 19 --- .../installer-cross-repo-fix-2026-08-24.json | 19 --- .../nftables-drift-self-check-2026-08-30.json | 17 --- .../nvidia-config-tests-2026-08-23.json | 19 --- .../python-314-lint-target-2026-08-29.json | 18 --- .../split-boot-repair-2026-08-23.json | 19 --- .../contracts/suite-speedup-2026-08-23.json | 17 --- .../thorium-repairs-tests-2026-08-23.json | 18 --- .../token-audit-batching-axis-2026-08-28.json | 16 --- .../ubuntu-perf-fixes-tests-2026-08-23.json | 19 --- .../website-admin-editor-unit-2026-08-29.json | 17 --- ...ire-shell-coverage-ratchet-2026-08-23.json | 19 --- .../evidence/a11ydump-lock-2026-09-25.json | 36 ----- .../android-ui-hint-labels-2026-08-27.json | 40 ------ .../app-icons-bowl-star-2026-08-29.json | 45 ------- .../app-icons-umbrella-2026-09-25.json | 43 ------ .../ci-green-hint-push-2026-08-29.json | 36 ----- .../dependency-freshness-gate-2026-08-28.json | 33 ----- ...-alarm-leg-morning-routine-2026-08-30.json | 43 ------ ...-phone-pull-skip-processed-2026-10-09.json | 43 ++++++ ...x-precommit-utils-checkout-2026-08-25.json | 48 ------- ...us-owner-icon-registration-2026-08-24.json | 48 ------- .../g29-ffb-new-lg4ff-2026-09-14.json | 42 ------ .../g29-shifter-hid-bpf-2026-09-14.json | 37 ----- .../g29-shifter-no-notify-2026-09-18.json | 35 ----- .../g29-shifter-ps4-mode-2026-09-18.json | 47 ------- ...yring-unlock-secretstorage-2026-09-20.json | 38 ------ .../helium-ublock-origin-mv2-2026-09-20.json | 53 -------- .../homeassistant-viomi-2026-09-27.json | 47 ------- .../i3blocks-gaming-budget-2026-09-26.json | 44 ------ .../i3blocks-screen-locker-2026-08-29.json | 40 ------ .../installer-cross-repo-fix-2026-08-24.json | 67 ---------- .../evidence/journal-cap-4g-2026-09-13.json | 50 ------- .../md-naming-namespaces-2026-08-24.json | 48 ------- ...ew-lg4ff-conditional-regen-2026-09-14.json | 28 ---- .../nftables-docker-forward-2026-08-29.json | 42 ------ .../nftables-drift-self-check-2026-08-30.json | 73 ---------- .../nftables-drift-verify-2026-08-30.json | 40 ------ ...erf-probes-loadavg-fixture-2026-09-14.json | 29 ---- .../evidence/phone-guard-2026-09-25.json | 39 ------ .../evidence/phone-lease-2026-09-20.json | 33 ----- .../phone-lease-scopes-2026-09-25.json | 45 ------- ...prettier-gate-result-cache-2026-09-11.json | 47 ------- .../python-314-lint-target-2026-08-29.json | 44 ------ .../register-betting-sim-icon-2026-08-28.json | 36 ----- .../register-book-guard-icon-2026-09-26.json | 31 ----- ...pair-stale-live-references-2026-08-23.json | 78 ----------- .../syncyomi-local-fork-build-2026-08-28.json | 47 ------- .../token-audit-batching-axis-2026-08-28.json | 44 ------ .../website-admin-editor-unit-2026-08-29.json | 48 ------- ...bsite-blog-directory-index-2026-08-29.json | 36 ----- .../website-force-recreate-2026-08-29.json | 42 ------ python_pkg/endurain_import/__main__.py | 8 +- python_pkg/endurain_import/sources.py | 41 +++++- .../endurain_import/tests/test_phone_pull.py | 126 ++++++++++++++++++ 70 files changed, 211 insertions(+), 2236 deletions(-) delete mode 100644 docs/superpowers/contracts/archive-billsplit-2026-08-23.json delete mode 100644 docs/superpowers/contracts/archive-bucket-catch-poker-stakes-2026-08-23.json delete mode 100644 docs/superpowers/contracts/ci-mirror-scoped-push-gate-2026-08-23.json delete mode 100644 docs/superpowers/contracts/endurain-db-dedupe-2026-08-23.json delete mode 100644 docs/superpowers/contracts/endurain-import-dedupe-and-adb-serial-2026-08-23.json delete mode 100644 docs/superpowers/contracts/endurain-runnerup-webdav-2026-08-23.json delete mode 100644 docs/superpowers/contracts/extract-android-guardian-2026-08-23.json delete mode 100644 docs/superpowers/contracts/extract-digital-wellbeing-2026-08-23.json delete mode 100644 docs/superpowers/contracts/extract-hosts-blocker-2026-08-23.json delete mode 100644 docs/superpowers/contracts/extract-mtk-root-2026-08-23.json delete mode 100644 docs/superpowers/contracts/extract-phone-focus-mode-2026-08-23.json delete mode 100644 docs/superpowers/contracts/extract-system-maintenance-2026-08-23.json delete mode 100644 docs/superpowers/contracts/extract-three-apps-2026-08-23.json delete mode 100644 docs/superpowers/contracts/flatten-i3-and-misc-2026-08-23.json delete mode 100644 docs/superpowers/contracts/flatten-linux-configuration-2026-08-23.json delete mode 100644 docs/superpowers/contracts/hosts-guard-tests-2026-08-23.json delete mode 100644 docs/superpowers/contracts/installer-cross-repo-fix-2026-08-24.json delete mode 100644 docs/superpowers/contracts/nftables-drift-self-check-2026-08-30.json delete mode 100644 docs/superpowers/contracts/nvidia-config-tests-2026-08-23.json delete mode 100644 docs/superpowers/contracts/python-314-lint-target-2026-08-29.json delete mode 100644 docs/superpowers/contracts/split-boot-repair-2026-08-23.json delete mode 100644 docs/superpowers/contracts/suite-speedup-2026-08-23.json delete mode 100644 docs/superpowers/contracts/thorium-repairs-tests-2026-08-23.json delete mode 100644 docs/superpowers/contracts/token-audit-batching-axis-2026-08-28.json delete mode 100644 docs/superpowers/contracts/ubuntu-perf-fixes-tests-2026-08-23.json delete mode 100644 docs/superpowers/contracts/website-admin-editor-unit-2026-08-29.json delete mode 100644 docs/superpowers/contracts/wire-shell-coverage-ratchet-2026-08-23.json delete mode 100644 docs/superpowers/evidence/a11ydump-lock-2026-09-25.json delete mode 100644 docs/superpowers/evidence/android-ui-hint-labels-2026-08-27.json delete mode 100644 docs/superpowers/evidence/app-icons-bowl-star-2026-08-29.json delete mode 100644 docs/superpowers/evidence/app-icons-umbrella-2026-09-25.json delete mode 100644 docs/superpowers/evidence/ci-green-hint-push-2026-08-29.json delete mode 100644 docs/superpowers/evidence/dependency-freshness-gate-2026-08-28.json delete mode 100644 docs/superpowers/evidence/drop-alarm-leg-morning-routine-2026-08-30.json create mode 100644 docs/superpowers/evidence/endurain-phone-pull-skip-processed-2026-10-09.json delete mode 100644 docs/superpowers/evidence/fix-precommit-utils-checkout-2026-08-25.json delete mode 100644 docs/superpowers/evidence/focus-owner-icon-registration-2026-08-24.json delete mode 100644 docs/superpowers/evidence/g29-ffb-new-lg4ff-2026-09-14.json delete mode 100644 docs/superpowers/evidence/g29-shifter-hid-bpf-2026-09-14.json delete mode 100644 docs/superpowers/evidence/g29-shifter-no-notify-2026-09-18.json delete mode 100644 docs/superpowers/evidence/g29-shifter-ps4-mode-2026-09-18.json delete mode 100644 docs/superpowers/evidence/helium-keyring-unlock-secretstorage-2026-09-20.json delete mode 100644 docs/superpowers/evidence/helium-ublock-origin-mv2-2026-09-20.json delete mode 100644 docs/superpowers/evidence/homeassistant-viomi-2026-09-27.json delete mode 100644 docs/superpowers/evidence/i3blocks-gaming-budget-2026-09-26.json delete mode 100644 docs/superpowers/evidence/i3blocks-screen-locker-2026-08-29.json delete mode 100644 docs/superpowers/evidence/installer-cross-repo-fix-2026-08-24.json delete mode 100644 docs/superpowers/evidence/journal-cap-4g-2026-09-13.json delete mode 100644 docs/superpowers/evidence/md-naming-namespaces-2026-08-24.json delete mode 100644 docs/superpowers/evidence/new-lg4ff-conditional-regen-2026-09-14.json delete mode 100644 docs/superpowers/evidence/nftables-docker-forward-2026-08-29.json delete mode 100644 docs/superpowers/evidence/nftables-drift-self-check-2026-08-30.json delete mode 100644 docs/superpowers/evidence/nftables-drift-verify-2026-08-30.json delete mode 100644 docs/superpowers/evidence/perf-probes-loadavg-fixture-2026-09-14.json delete mode 100644 docs/superpowers/evidence/phone-guard-2026-09-25.json delete mode 100644 docs/superpowers/evidence/phone-lease-2026-09-20.json delete mode 100644 docs/superpowers/evidence/phone-lease-scopes-2026-09-25.json delete mode 100644 docs/superpowers/evidence/prettier-gate-result-cache-2026-09-11.json delete mode 100644 docs/superpowers/evidence/python-314-lint-target-2026-08-29.json delete mode 100644 docs/superpowers/evidence/register-betting-sim-icon-2026-08-28.json delete mode 100644 docs/superpowers/evidence/register-book-guard-icon-2026-09-26.json delete mode 100644 docs/superpowers/evidence/repair-stale-live-references-2026-08-23.json delete mode 100644 docs/superpowers/evidence/syncyomi-local-fork-build-2026-08-28.json delete mode 100644 docs/superpowers/evidence/token-audit-batching-axis-2026-08-28.json delete mode 100644 docs/superpowers/evidence/website-admin-editor-unit-2026-08-29.json delete mode 100644 docs/superpowers/evidence/website-blog-directory-index-2026-08-29.json delete mode 100644 docs/superpowers/evidence/website-force-recreate-2026-08-29.json create mode 100644 python_pkg/endurain_import/tests/test_phone_pull.py diff --git a/docs/superpowers/contracts/archive-billsplit-2026-08-23.json b/docs/superpowers/contracts/archive-billsplit-2026-08-23.json deleted file mode 100644 index 6bdea775..00000000 --- a/docs/superpowers/contracts/archive-billsplit-2026-08-23.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "title": "Archive billsplit together with every artifact coupled to it", - "objective": "Remove the billsplit Flutter app from testsAndMisc without leaving the repo in a state where a gate fails or a config rule points at a missing path. billsplit is the coupled case the earlier archive commit deliberately deferred: a coverage package, an app_icons registry entry, a CI workflow and two paired binary rules all name it.", - "acceptance_criteria": [ - "billsplit/ and python_pkg/billsplit_coverage/ are both present in kuhyx/testsAndMisc-archive, confirmed via the GitHub API before the local delete.", - "Archived commit count equals the monorepo count for those paths (8), and tracked file counts match exactly (73 and 5).", - "python_pkg/app_icons/apps.py no longer contains a billsplit AppIcon entry, and the app_icons registry still imports cleanly.", - "The repo-wide pytest gate still reports 100% coverage, with the test count reduced by exactly the 19 billsplit_coverage tests and nothing else.", - "check_no_binaries.sh exits 0 with a .binary-allowlist that now carries no patterns.", - ".gitignore contains no billsplit rule; the ! overrides are removed in the same commit as the allowlist patterns they paired with, so the two halves never disagree.", - "No file under .github/workflows/ references billsplit or billsplit_coverage.", - "AGENTS.md no longer presents billsplit as a live app, while keeping the both-halves guidance for any future Flutter app.", - "pre-commit passes without --no-verify." - ], - "out_of_scope": [ - "Removing python_pkg/app_icons itself, which serves eleven other apps.", - "Changing how check_no_binaries.sh or the coverage gate work; this commit only removes data they read.", - "Historical planning documents that mention billsplit as past work." - ], - "verifier": "gh api confirms both paths in the archive; git ls-files counts match 73/5; diff -r clean; python3 meta/scripts/pytest_changed_packages.py python_pkg/app_icons/apps.py reports 2095 passed at 100%; ./meta/scripts/check_no_binaries.sh exits 0; ./meta/scripts/check_directory_depth.sh --all drops 451 -> 417; git grep billsplit finds only intentional prose." -} diff --git a/docs/superpowers/contracts/archive-bucket-catch-poker-stakes-2026-08-23.json b/docs/superpowers/contracts/archive-bucket-catch-poker-stakes-2026-08-23.json deleted file mode 100644 index 2cd7a70e..00000000 --- a/docs/superpowers/contracts/archive-bucket-catch-poker-stakes-2026-08-23.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "title": "Archive bucket_catch and poker-stakes to testsAndMisc-archive", - "objective": "Remove two self-contained apps from testsAndMisc, preserving their full git history in kuhyx/testsAndMisc-archive, as the zero-risk opening step of the repo restructure. Neither unit is referenced by any other code, systemd unit, or symlink, which is why they go first.", - "acceptance_criteria": [ - "Both directories are present in kuhyx/testsAndMisc-archive at their original repo-relative paths, confirmed through the GitHub API rather than a local clone.", - "The archived commit count for each unit equals the number of monorepo commits touching it: bucket_catch 10, poker-stakes 2.", - "diff -r between the archived copy and the pre-removal working tree reports no content differences for either unit.", - "Tracked file counts match exactly: bucket_catch 64, poker-stakes 65.", - ".github/workflows/poker-stakes-ci.yml is removed in the same commit as the app it builds, so no workflow is left pointing at a missing path.", - "No file under .github/workflows/ still references either unit.", - "meta/scripts/report_live_state.sh output is unchanged, proving neither unit had live system state pointing at it.", - "pre-commit passes without --no-verify." - ], - "out_of_scope": [ - "billsplit, which is archived separately because it is coupled to python_pkg/billsplit_coverage and an app_icons registry entry that must be deleted in the same commit.", - "Rewriting historical planning notes that mention these names; they are dated records, not live configuration.", - "Wiring the directory-depth gate, which stays a dry run until the tree conforms." - ], - "verifier": "gh api repos/kuhyx/testsAndMisc-archive/contents lists both units; git ls-files counts match source; diff -r is clean; ./meta/scripts/check_directory_depth.sh --all drops from 556 to 451; ./meta/scripts/report_live_state.sh still reports 27 reference lines; pre-commit run over the staged set passes." -} diff --git a/docs/superpowers/contracts/ci-mirror-scoped-push-gate-2026-08-23.json b/docs/superpowers/contracts/ci-mirror-scoped-push-gate-2026-08-23.json deleted file mode 100644 index 4e947c98..00000000 --- a/docs/superpowers/contracts/ci-mirror-scoped-push-gate-2026-08-23.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "title": "Scope the pre-push CI mirror to the push range, and make its pytest stage actually run", - "objective": "Take `git push` from ~130s to seconds by checking what the push changed rather than the whole tree, while repairing the python test stage, which returned success without executing a single test.", - "acceptance_criteria": [ - "ci_mirror.sh passes the changed paths to pytest_changed_packages.py, so the stage runs the suite instead of short-circuiting on empty argv.", - "A push whose range contains a failing python test is refused with exit 1.", - "A push whose range touches a lib/tests suite with a failing assertion is refused with exit 1, and the suite fails bare first so the gate is not the only evidence.", - "The green-tree cache is NOT written when the gate fails, so a red run cannot poison the short-circuit.", - "An unknown range, or a change to .pre-commit-config.yaml / meta/scripts/ / requirements.txt / .githooks/, forces a full --all-files sweep.", - "A change under scripts/lib/ or to a suite's lib_test_core.sh / lib_test_path.sh selects every lib/tests suite, not just the enclosing one.", - "Repo-wide hooks (file-length-cap --all, no-binaries, check-python-location) still run on every push.", - "No test is weakened or deleted, no --no-verify is added to any script, and pre-commit's own 4.8s commit-stage run is untouched.", - "All three scripts are shellcheck-clean, shfmt-clean and within the 250-line cap." - ], - "out_of_scope": [ - "Moving ci-mirror off pre-push (option 4 in the handoff): the user chose to keep the guarantee that a red push cannot reach GitHub.", - "Changing the coverage bar or the jail instrument.", - "Touching the pre-commit hook stage, already measured at 4.8s." - ], - "verifier": "With PRE_COMMIT_FROM_REF/PRE_COMMIT_TO_REF pinned to a probe commit: `bash meta/scripts/ci_mirror.sh` exits 0 on a green range and 1 on a range containing a deliberately broken python assertion or shell assertion; `time` on a docs-only, python and shell range reports 7.6s / 25.3s / 43.6s against a ~130s baseline; `shellcheck -x`, `shfmt -l` and `check_file_length.sh --all` are clean." -} diff --git a/docs/superpowers/contracts/endurain-db-dedupe-2026-08-23.json b/docs/superpowers/contracts/endurain-db-dedupe-2026-08-23.json deleted file mode 100644 index d4a148dd..00000000 --- a/docs/superpowers/contracts/endurain-db-dedupe-2026-08-23.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "title": "endurain importer dedupe via postgres", - "objective": "Give the importer a working answer to 'does Endurain already hold this run?', now that the HTTP route cannot provide one: API keys are upload-only and the activities read endpoint is JWT-only, so the existing check always returned None and every pass relied on the local ledger alone.", - "acceptance_criteria": [ - "A run Endurain already holds is skipped even when the local ledger is empty, and the activity count does not grow.", - "The same run IS imported when the fallback is disabled, proving the check is what blocks it.", - "A dead or renamed container yields None (ledger fallback), while a user with no activities yields an empty list; the two are never conflated.", - "The journal distinguishes which backend answered, so a DB-sourced pass is not mistaken for a ledger-only one.", - "Verified in the real systemd unit, not only from an interactive shell.", - "100% branch coverage on the new and changed modules, with no lint suppressions added.", - "No test touches the real postgres container." - ], - "out_of_scope": [ - "Removing or replacing the HTTP lookup - it stays first in the chain so the supported path resumes working by itself if Endurain widens the API-key scope allow-list.", - "Exposing a postgres host port or adding a database driver dependency.", - "Patching Endurain's own source or forging scopes in users_api_keys to work around the upload-only allow-list." - ], - "verifier": "pre-commit + python_pkg/endurain_import/tests + a real systemctl --user run with journal inspection" -} diff --git a/docs/superpowers/contracts/endurain-import-dedupe-and-adb-serial-2026-08-23.json b/docs/superpowers/contracts/endurain-import-dedupe-and-adb-serial-2026-08-23.json deleted file mode 100644 index 75111c3d..00000000 --- a/docs/superpowers/contracts/endurain-import-dedupe-and-adb-serial-2026-08-23.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "title": "Teach the endurain importer to skip runs already in Endurain, and pin the adb fallback to one device", - "objective": "RunnerUp can now upload to Endurain directly as well as via the WebDAV inbox, and the two paths cannot see each other: the same run lands twice (activities 14 and 15, identical start time, distances differing only by tcx/gpx rounding). Add a third dedupe check that asks Endurain what it already holds, and fix the adb fallback, which fails on every call the moment a second device is attached.", - "acceptance_criteria": [ - "A run already present in Endurain is moved to processed/ without being uploaded again, even when the local ledger has never seen it.", - "A failed or unauthorised remote query returns None and falls back to the existing ledger dedupe. None is never read as 'no matching activity exists', because that would upload a duplicate on every transport hiccup.", - "Start times are compared with a tolerance: the file for activity 15 embeds 21:51:04Z while Endurain stored 21:51:05Z, so exact equality misses the very duplicate that motivated this work.", - "Export filenames are parsed as LOCAL wall-clock time; RunnerUp_2026-08-14-00-58-45_Running resolves to 2026-08-13 22:58:45 UTC, not 2026-08-14.", - "With several adb devices attached and no serial pinned, the phone fallback is skipped with a warning naming the attached serials and the env var, instead of failing every call with 'more than one device/emulator'.", - "ENDURAIN_ADB_SERIAL selects the device when set; a pinned serial that is not attached is refused rather than silently ignored.", - "Every adb invocation carries -s ; no serial is hardcoded, since this repo is public.", - "The five package modules keep 100% branch coverage with no lint suppressions and no coverage exclusions.", - "The phone-side files are never written, moved or deleted: screen-locker reads the same directory as its unlock gate." - ], - "out_of_scope": [ - "Deleting duplicates that already exist. This work prevents new ones; clearing the 14/15 collision needs session credentials and is the user's action.", - "Granting the API key the activities:read scope. Both existing keys hold activities:upload only, so the remote check is inert until that changes; the fail-closed path is what ships working today.", - "Changing the WebDAV inbox, the dufs config, or RunnerUp's own uploader.", - "Recording the returned activity_id in the ledger. It is null on every existing entry, which is cosmetic and unrelated." - ], - "verifier": ".venv/bin/python -m pytest python_pkg/endurain_import/tests/ -q (expects 85 passed), plus --cov=python_pkg.endurain_import showing 100% with zero missed branches on all five modules, plus .venv/bin/python -m ruff check python_pkg/endurain_import/, plus systemctl --user start endurain-import.service with two devices attached showing the named-serials warning rather than 'more than one device/emulator'." -} diff --git a/docs/superpowers/contracts/endurain-runnerup-webdav-2026-08-23.json b/docs/superpowers/contracts/endurain-runnerup-webdav-2026-08-23.json deleted file mode 100644 index 5cf6ab64..00000000 --- a/docs/superpowers/contracts/endurain-runnerup-webdav-2026-08-23.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "title": "Endurain viewer fed by RunnerUp WebDAV exports", - "objective": "Stand up self-hosted Endurain behind the existing Caddy edge and import RunnerUp activities into it, primarily over WebDAV from the phone with an adb pull as fallback. Endurain is a VIEWER only: screen-locker's workout gate keeps reading the phone over adb and is not modified, because a WebDAV drop folder is spoofable by anything that can write to ~/data/cloud and must never become a screen-unlock authority. Success means real activities from the phone are visible in Endurain, importing twice never creates a duplicate, and screen-locker still passes its full suite.", - "acceptance_criteria": [ - "Endurain reachable at https://endurain.kuhy.duckdns.org with a valid Let's Encrypt certificate", - "At least 4 real RunnerUp activities imported and visible, with correct distance and duration", - "Re-running the importer over already-imported files creates ZERO new activities", - "The .gpx and .tcx of a single run import as ONE activity, not two", - "The seeded admin/admin login is rejected before the service is ever published", - "The phone-facing WebDAV credential cannot read ~/data/cloud/Keepass or gdrive_backups", - "screen-locker's full test suite still passes at 100% coverage" - ], - "out_of_scope": [ - "Any edit to screen-locker's _runnerup_verification.py, _runnerup_tcx.py or _runnerup_db.py", - "Full history backfill out of RunnerUp's SQLite DB (the phone holds only 5 files)", - "Strava and Garmin Connect integration", - "Patching RunnerUp to add background auto-upload (it has none by design)" - ], - "verifier": "pre-commit run --files ; pytest python_pkg/endurain_import/tests/ (52 tests, 100% branch coverage); systemctl --user start endurain-import.service; psql count against endurain-postgres; screen-locker pytest suite (1090 tests)" -} diff --git a/docs/superpowers/contracts/extract-android-guardian-2026-08-23.json b/docs/superpowers/contracts/extract-android-guardian-2026-08-23.json deleted file mode 100644 index 8b5e2e0e..00000000 --- a/docs/superpowers/contracts/extract-android-guardian-2026-08-23.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "title": "Extract android_guardian and settle the heavy_job_lock seam", - "objective": "Move the Magisk module to its own public repo. The directory it lived in also held heavy_job_lock.sh, a shared lock library that is NOT part of the module, so this extraction has to split the directory rather than move it wholesale. It also inherits a packaging script whose module path was already broken before the extraction.", - "acceptance_criteria": [ - "The module and both its test suites are in the new public repo, tests passing from a clean clone, shellcheck exiting 0 with no severity filter.", - "heavy_job_lock.sh lands where its installer is, justified by measuring which consumers read the repo copy versus the installed /usr/local/bin copy - not by the ad-hoc '>2 consumers' rule.", - "install_pacman_wrapper.sh's relative path to heavy_job_lock.sh resolves after the move.", - "update_android_hosts.sh points at a real location, and a missing checkout fails with an actionable message at a single funnel rather than a bare cp error deep in a helper.", - "The module still builds end-to-end from the new checkout.", - "shell-tests.yml names no removed suite; the coverage allowlist names no removed path.", - "periodic_background/utils/ is fully emptied, not left as a one-file directory.", - "pre-commit passes without --no-verify." - ], - "out_of_scope": [ - "Moving heavy_job_lock.sh to ~/src/utils: the measurement does not support it, and every consumer but the installer already reads the installed copy.", - "The adb push half of update_android_hosts.sh, which needs a device in a writable state.", - "digital_wellbeing itself, which is the next and last extraction." - ], - "verifier": "bash tests/*.sh in the new repo passes; shellcheck exits 0 there; ANDROID_GUARDIAN_DIR=/nonexistent produces the guard message; a real run builds the zip (182331 domains); shellcheck and check_shell_coverage.sh --all clean here; check_directory_depth.sh --all continues to fall." -} diff --git a/docs/superpowers/contracts/extract-digital-wellbeing-2026-08-23.json b/docs/superpowers/contracts/extract-digital-wellbeing-2026-08-23.json deleted file mode 100644 index fe3ab309..00000000 --- a/docs/superpowers/contracts/extract-digital-wellbeing-2026-08-23.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "title": "Extract digital_wellbeing, move its six test suites with it, and make the directory-depth cap enforce", - "objective": "Clear the last 41 directory-depth violations by moving the unit to github.com/kuhyx/digital-wellbeing, then delete --warn from the depth hook so the cap is real -- the point of the whole campaign.", - "acceptance_criteria": [ - "check_directory_depth.sh --all exits 0 with --warn removed from the hook entry.", - "The extracted repo's history spans all three paths the unit has lived at (63 commits), plus the 24 from the separately-filtered test suites.", - "All 80 tracked files are byte-identical to the monorepo copies; node_modules and __pycache__ are excluded.", - "All six moved suites plus every lib/tests/run_all.sh pass from ~/src/digital-wellbeing, with no root, pacman or Arch userland -- verified under env -i.", - "Every moved test name is deleted from BOTH hardcoded arrays in .github/workflows/shell-tests.yml; every name still listed there resolves to a file that exists.", - "check_and_enable_services.sh runs to completion and reports ok for pacman_wrapper, makepkg_wrapper, midnight_shutdown, startup_monitor, periodic_systems and hosts.", - "The pacman-wrapper drift manifest verifies with sha256sum -c after regeneration.", - "report_live_state.sh --check reports ZERO stale linux_configuration/scripts/ references (down from 8).", - "No lint suppression is added anywhere." - ], - "out_of_scope": [ - "Fixing the unwrapped discord/signal-desktop binaries -- pre-existing live state, identical at baseline (18 passed / 1 failed / 5 skipped both before and after).", - "The workout_locker error, which concerns the separate screen-locker repo.", - "The Nextcloud dedup, measured and recommended against in the handoff.", - "Reformatting vendor/common.sh, which is a verbatim copy." - ], - "verifier": "./meta/scripts/check_directory_depth.sh --all exits 0; ~/src/digital-wellbeing/tests/run_all.sh passes all seven suites; grep for the six moved test names in shell-tests.yml returns 0; sudo check_and_enable_services.sh completes with no unbound-variable error and no missing-script error; sudo sha256sum -c --status /var/lib/pacman-wrapper/source.sha256 exits 0; report_live_state.sh --check greps 0 for linux_configuration/scripts/; shellcheck clean on all nine changed shell files." -} diff --git a/docs/superpowers/contracts/extract-hosts-blocker-2026-08-23.json b/docs/superpowers/contracts/extract-hosts-blocker-2026-08-23.json deleted file mode 100644 index aefe34e1..00000000 --- a/docs/superpowers/contracts/extract-hosts-blocker-2026-08-23.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "title": "Extract the hosts blocker into its own repo and repoint its six monorepo callers", - "objective": "Move linux_configuration/periodic_background/hosts/ to github.com/kuhyx/hosts-blocker with full history, clearing 5 of 46 directory-depth violations, without changing a single source line or disturbing the live /etc/hosts guards.", - "acceptance_criteria": [ - "The extracted repo's history spans all four paths the unit has lived at, not just the current one — 52 commits, reaching the original 'block websites uising etc hosts'.", - "Every extracted source file is byte-identical to its monorepo copy (diff -r reports no differences).", - "lib/tests/run_all.sh passes from ~/src/hosts-blocker, the real clone location, with 25 assertions.", - "All six monorepo callers resolve to /home/kuhy/src/hosts-blocker, including under sudo, where $HOME would be /root.", - "A missing checkout fails loudly with a clone instruction and a non-zero return — never a silent skip.", - "check_directory_depth.sh --all reports 41 violations, all of them digital_wellbeing.", - "report_live_state.sh --check output is byte-identical before and after: no new stale references.", - "The installed guard plugins under /usr/local/share/guard-lib-plugins/ remain byte-identical to the extracted copies.", - "No lint suppression (shellcheck disable, noqa, per-file ignore) is added anywhere." - ], - "out_of_scope": [ - "Extracting digital_wellbeing (41 violations) — the next, harder unit.", - "Removing --warn from the directory-depth-cap hook, which is only valid once BOTH units are out.", - "Reformatting guard/plugins/*.sh with shfmt; they are unformatted in the monorepo too and their installed copies must stay identical.", - "Re-running the hosts installer or otherwise touching the live /etc/hosts, its chattr flags, or the running guards.", - "Fixing the 8 pre-existing stale live-state references — a separate commit after both extractions.", - "Deleting .hippo/ or the untracked extraction residue — separate commits." - ], - "verifier": "diff -r --exclude=.git between the monorepo unit and the extracted repo reports IDENTICAL; ~/src/hosts-blocker/lib/tests/run_all.sh exits 0 with 25 passed; pre-commit run --all-files --hook-stage pre-push in the extracted repo passes with no file modified; shellcheck is clean on the helper and all six callers; sudo -n resolution prints /home/kuhy/src/hosts-blocker; HOSTS_BLOCKER_DIR=/nonexistent require_extracted_repo returns 1 with a clone instruction; check_directory_depth.sh --all reports 41; diff of report_live_state.sh --check before/after is empty." -} diff --git a/docs/superpowers/contracts/extract-mtk-root-2026-08-23.json b/docs/superpowers/contracts/extract-mtk-root-2026-08-23.json deleted file mode 100644 index 73037ee6..00000000 --- a/docs/superpowers/contracts/extract-mtk-root-2026-08-23.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "title": "Extract mtk_root together with the libraries that live outside it", - "objective": "Move the MediaTek rooting toolkit to its own public repo. Unlike the three app extractions, mtk_root's libraries and half its test suites live in linux_configuration/scripts/lib/, one directory up. Extracting the directory alone would orphan eight files here and leave the new repo unable to source anything.", - "acceptance_criteria": [ - "The new repo preserves the mtk_root/ + lib/ sibling layout, so every `source \"$SCRIPT_DIR/../lib/...\"` resolves without editing a single call site.", - "All 84 mtk tests pass from a clean clone of the new repo.", - "shellcheck exits 0 over every script in the new repo with no severity filter, which requires carrying .shellcheckrc across.", - "The new repo has its own lib/tests/run_all.sh, because the monorepo's is shared with a non-mtk suite and must stay.", - "The suite remaining in the monorepo still passes: removing a shared fixture must not take test_common_datetime.sh down with it.", - "meta/shell-coverage-allowlist.txt has no entry naming a removed path.", - "check_shell_coverage.sh --all exits 0.", - "pre-commit passes without --no-verify." - ], - "out_of_scope": [ - "single_use/utils/lib/bl9000_*.sh, which is separate Android rooting tooling that merely mentions mtkclient.", - "Renaming mtk_harness.sh, whose name is now historical but whose one consumer would churn for no behavioural gain.", - "Verifying the toolkit against real hardware, which READY.md already records as pending." - ], - "verifier": "./lib/tests/run_all.sh in the new repo reports 84 passed / 0 failed; shellcheck over the new repo exits 0; ./linux_configuration/scripts/lib/tests/run_all.sh here reports 55 passed / 0 failed; bash meta/scripts/check_shell_coverage.sh --all exits 0; gh confirms the repo is public on main." -} diff --git a/docs/superpowers/contracts/extract-phone-focus-mode-2026-08-23.json b/docs/superpowers/contracts/extract-phone-focus-mode-2026-08-23.json deleted file mode 100644 index 42be5ddd..00000000 --- a/docs/superpowers/contracts/extract-phone-focus-mode-2026-08-23.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "title": "Extract phone_focus_mode with focus_policy into one public repo", - "objective": "Move the GPS focus enforcer and its Python policy half out together, since focus_policy parses phone_focus_mode's config.sh and is referenced by nothing else. The repo is public and the subject matter is a home location, so proving no coordinate leaks is part of the job, not an afterthought.", - "acceptance_criteria": [ - "Both trees land in one public repo with full history and python_pkg.focus_policy imports rewritten.", - "The full git history contains no real home coordinate and no committed config_secrets.sh, verified BEFORE the repo is made public.", - "The new repo has a .gitignore covering config_secrets.sh; none came across in the filter-repo pass.", - "The Python suites pass from a clean clone: 161 tests.", - "The shell suites pass with NO config_secrets.sh present, which means the harness must pin its own coordinates instead of inheriting real ones.", - "config.sh can be sourced without a secrets file, so CI and a fresh clone work.", - "Any suite that does not pass is skipped in CI with a stated reason, and its behaviour is compared against the pre-extraction baseline to establish whether the split caused it.", - "The monorepo's remaining tests still pass and no config, hook or workflow names a removed path.", - "pre-commit passes without --no-verify." - ], - "out_of_scope": [ - "hosts and digital_wellbeing, deferred while the guard-lib repoint is pending.", - "Fixing test_magisk_service.sh, which was already broken and is not a regression from this split.", - "python-tests.yml's stale lichess_bot and tests/ path filters, absent since May 2026." - ], - "verifier": "git log --all -p greps clean for the home coordinates; pytest reports 161 passed in the new repo; the shell suites run with the secrets file moved away; pytest linux_configuration/tests reports 178 passed here; check_shell_coverage.sh --all and check_python_location.sh over every .py both exit 0; check_directory_depth.sh --all drops 262 -> 248." -} diff --git a/docs/superpowers/contracts/extract-system-maintenance-2026-08-23.json b/docs/superpowers/contracts/extract-system-maintenance-2026-08-23.json deleted file mode 100644 index ce810102..00000000 --- a/docs/superpowers/contracts/extract-system-maintenance-2026-08-23.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "title": "Extract system-maintenance, the first of the four live subsystems", - "objective": "Move the periodic maintenance subsystem to its own public repo. It is the isolated one -- no cross-unit source edges -- so it goes first and establishes the pattern for the three harder ones. Its Python tests live in linux_configuration/tests/ and reach it through a shared conftest, so the test half must move with it without breaking the suites that stay.", - "acceptance_criteria": [ - "A vmbox pass first: the installer runs from an extracted-layout copy in the guest before anything is changed on the host, and the result is reported as sandbox-only.", - "The new repo's Python tests pass from a clean clone with its own conftest; 136 tests.", - "Both extracted shell suites pass after their hardcoded monorepo paths are fixed, and shellcheck exits 0 with no severity filter.", - "The new repo declares BOTH CI halves, since python-tests.yml reached it by paths filter and shell-tests.yml by name list, and neither follows the code out.", - "The suites remaining in the monorepo still pass: 202 Python tests.", - "The cross-unit pair test test_shutdown_timer_monitor.sh keeps working, and does not silently degrade into asserting against a stale installed binary.", - "No workflow, conftest, pyproject sys.path entry or coverage-allowlist line names a removed path.", - "pre-commit passes without --no-verify." - ], - "out_of_scope": [ - "Refreshing the stale /usr/local/bin/shutdown-timer-monitor.sh, which digital_wellbeing's installer owns.", - "The other three subsystems.", - "Moving the shared linux_configuration/tests/conftest.py, which serves several script directories that stay." - ], - "verifier": "vm run in guest exercises the installer; python -m pytest tests -q reports 136 passed in the new repo; bash tests/*.sh and shellcheck both clean there; pytest linux_configuration/tests reports 202 passed here; bash linux_configuration/tests/test_shutdown_timer_monitor.sh passes with the checkout and SKIPs without it; check_shell_coverage.sh --all exits 0; check_directory_depth.sh --all drops 294 -> 267." -} diff --git a/docs/superpowers/contracts/extract-three-apps-2026-08-23.json b/docs/superpowers/contracts/extract-three-apps-2026-08-23.json deleted file mode 100644 index 50d7027c..00000000 --- a/docs/superpowers/contracts/extract-three-apps-2026-08-23.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "title": "Extract reverse_survivors, kcd2_dice_solver and focus_owner to their own public repos", - "objective": "Move three self-contained applications out of testsAndMisc into their own repositories with full history, give each real CI it never had in the monorepo, and repoint every consumer the monorepo keeps - including the live systemd unit and Docker bind mount that deploy the dice solver.", - "acceptance_criteria": [ - "Each unit is a public GitHub repo on branch main whose commit count equals the monorepo's count for that path (8, 14, 30).", - "Each extracted repo passes its own toolchain from a clean clone: npm check+build; pnpm lint+test+build; flutter analyze --fatal-infos + test.", - "Each extracted repo carries a CI workflow that runs those same commands with no paths filter and no working-directory.", - "kcd2-dice-build.service starts successfully and exits 0 after being repointed - proven by running it, not by reading it.", - "https://dice.kuhy.duckdns.org/ returns HTTP 200 and serves the bundle hash produced by that run.", - "setup_kcd2_dice_solver.sh builds from the extracted repo, and its generated post-commit hook no longer filters on a kcd2_dice_solver/ path that can never match again.", - "No file outside docs/ or a historical note still points at a removed directory.", - "The repo-wide pytest gate and pre-commit both still pass, without --no-verify." - ], - "out_of_scope": [ - "phone_focus_mode and python_pkg/focus_policy, which move together in a later step.", - "The four system subsystems under periodic_background.", - "Reformatting focus_owner's Dart sources: the committed tree predates the current formatter and 15 files would change, which belongs in its own commit in the new repo." - ], - "verifier": "gh api confirms three public repos; standalone clones pass npm/pnpm/flutter checks; systemctl --user start kcd2-dice-build.service exits 0; curl https://dice.kuhy.duckdns.org/ returns 200 with a bundle hash matching dist/; ./meta/scripts/report_live_state.sh shows no unit pointing at a removed path; pre-commit passes." -} diff --git a/docs/superpowers/contracts/flatten-i3-and-misc-2026-08-23.json b/docs/superpowers/contracts/flatten-i3-and-misc-2026-08-23.json deleted file mode 100644 index 03cf6444..00000000 --- a/docs/superpowers/contracts/flatten-i3-and-misc-2026-08-23.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "title": "Flatten i3-configuration and misc/testsAndMisc-bash, and repair the references the first flatten missed", - "objective": "Finish the flattening for the two remaining pure-taxonomy nestings, and close out the path rewrites that the previous commit's regex did not reach. The first flatten proved that the moves are easy and the references are the risk, so this change is mostly about finding references that a diff review would not surface.", - "acceptance_criteria": [ - "i3 config no longer sits under periodic_background, where it never belonged; both resulting directories are at depth 2.", - "misc/ no longer contains a directory named after the repo it lives in.", - "The media-ignore rules from the moved .gitignore still apply at their new location, and no previously tracked file becomes ignored.", - "Every $VAR/scripts/... reference is rewritten, including the orchestrators that name the still-deferred subsystems.", - "Both i3blocks test suites pass, having been RUN rather than eyeballed.", - "The repo-wide pytest gate still reports 100%.", - "Every $HOME symlink broken by the move is repointed, and the i3blocks scripts are run to prove the bar works.", - "pre-commit passes without --no-verify." - ], - "out_of_scope": [ - "digital_wellbeing and hosts, deferred pending the guard-lib repoint; their references are updated to the new scheme but the directories stay.", - "The ~57 remaining violations that encode real structure (a C project, Python package dirs, artgate's routes/styles, systemd unit dirs) rather than taxonomy." - ], - "verifier": "check_directory_depth.sh --all falls 139 -> 104; both test_i3blocks_*.sh pass; pytest_changed_packages.py reports 1798 passed at 100%; find ~ -xtype l finds nothing; git check-ignore confirms the relocated media rules; check_shell_coverage.sh --all exits 0." -} diff --git a/docs/superpowers/contracts/flatten-linux-configuration-2026-08-23.json b/docs/superpowers/contracts/flatten-linux-configuration-2026-08-23.json deleted file mode 100644 index 20a25f5b..00000000 --- a/docs/superpowers/contracts/flatten-linux-configuration-2026-08-23.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "title": "Dissolve linux_configuration/scripts/ and single_use/ to satisfy the depth-2 cap", - "objective": "Flatten the largest source of directory-depth violations. scripts/ and single_use/ were three levels of pure taxonomy carrying no information, and a 2-directory cap leaves no room for them. Categories move up to sit directly under linux_configuration/, which is what makes the cap satisfiable at all.", - "acceptance_criteria": [ - "linux_configuration/scripts/ no longer exists; its children sit one level under linux_configuration/.", - "Every relative `source` of a shared library resolves from its file's NEW location, checked by resolving each path on disk rather than by reading the diff.", - "Every REPO_ROOT derivation that walks up a fixed number of levels is recomputed for the new depth.", - "All lib/tests suites that passed before still pass, and any that fail are proven to fail identically in the pre-move baseline worktree.", - "The repo-wide pytest gate still reports 100% coverage.", - "Every symlink in $HOME pointing into the moved tree is repointed, and the i3blocks scripts are RUN to prove the status bar still works.", - "No config, workflow, allowlist or ignore rule names a path that no longer exists.", - "The .gitignore rules protecting untracked secrets still match after their paths change.", - "pre-commit passes without --no-verify." - ], - "out_of_scope": [ - "periodic_background/digital_wellbeing and hosts, deferred while the guard-lib repoint is pending; they keep their depth violations for now.", - "Deduplicating the two Nextcloud implementations, which is a separate change.", - "Wiring the depth gate, which lands only once the tree conforms." - ], - "verifier": "check_directory_depth.sh --all falls 248 -> 139; every '$SCRIPT_DIR/../lib/...' source resolves on disk; 8 of 9 lib/tests run_all.sh pass with the ninth proven to need the jail at baseline too; pytest_changed_packages.py reports 1798 passed at 100%; find ~ -xtype l finds no broken link; the i3blocks scripts emit real output." -} diff --git a/docs/superpowers/contracts/hosts-guard-tests-2026-08-23.json b/docs/superpowers/contracts/hosts-guard-tests-2026-08-23.json deleted file mode 100644 index 256e065a..00000000 --- a/docs/superpowers/contracts/hosts-guard-tests-2026-08-23.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "title": "Clear both hosts-guard libs and fix the done-redirect instrument artifact", - "objective": "Bring hosts_guard_migrate.sh and hosts_guard_rollback.sh to 100% measured line coverage and remove both allowlist exemptions, extending continuation_lines() so a loop terminator carrying a redirect stops being counted as an unreachable statement.", - "acceptance_criteria": [ - "Both libs measure 100.00% through tests/run_all.sh at --min 100 with --fail-on-case-error, exiting 0.", - "check_shell_coverage.sh passes on both with their allowlist lines already deleted.", - "The new _LOOP_END_OP predicate excludes ONLY lines where `done` is followed by a redirect or pipe; a bare `done` stays in the denominator so real uncovered loops are still caught.", - "An audit printing every newly-excluded line with its text shows exactly the two lines in hosts_guard_rollback.sh and nothing else across fixes/lib.", - "The instrument acceptance pair still measures exactly: dot_resolver_install.sh 39/39 and rpi_nc_ca.sh 73/73, through features/lib/tests/run_all.sh with its jail_args.", - "Both libs keep their production behaviour: every new seam defaults to the real path it replaced.", - "The suite stays green and shellcheck reports nothing, with zero suppressions." - ], - "out_of_scope": [ - "The backslash-continued-assignment artifact that caps bt_adapter.sh at 98.55%; it is a different predicate and only affects one line in one lib.", - "arch_sysctl_quarantined_cases.sh, which stays outside the suite.", - "The last two untested libs, ubuntu_perf_fixes.sh and nvidia_config.sh." - ], - "verifier": "timeout 900s bash meta/scripts/shell_coverage_jail.sh --timeout 600s --subject linux_configuration/scripts/single_use/fixes/lib/tests/run_all.sh --measure hosts_guard_migrate.sh (then hosts_guard_rollback.sh) --min 100 --fail-on-case-error -- \"\", then the acceptance pair through features/lib/tests/run_all.sh, then check_shell_coverage.sh on both libs." -} diff --git a/docs/superpowers/contracts/installer-cross-repo-fix-2026-08-24.json b/docs/superpowers/contracts/installer-cross-repo-fix-2026-08-24.json deleted file mode 100644 index f785109d..00000000 --- a/docs/superpowers/contracts/installer-cross-repo-fix-2026-08-24.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "title": "Make install_core_system.sh work on a fresh machine again", - "objective": "install_core_system.sh is the documented way to set this machine up from scratch, and it could not complete: two of its seven modules pointed at paths that stopped existing when screen_locker and steam_backlog_enforcer were extracted into their own repos, nothing installed guard-lib (so the CORE midnight-shutdown module always died with 'guardctl not found on PATH'), and the hosts blocking module applied chattr +i while the file-guard instances that are supposed to watch and re-enforce /etc/hosts were only ever registered by a one-shot script filed under fixes/. A fresh machine therefore got immutability with no watcher, no canonical copy and no bind mount.", - "acceptance_criteria": [ - "Every module in install_core_system.sh resolves to a script that exists, verified by running the installer end-to-end in a pristine vmbox guest that starts with none of the sibling repos checked out.", - "A missing sibling repo (screen-locker, steam-backlog-enforcer, hosts-blocker, digital-wellbeing, utils/guard-lib) is cloned automatically; if that clone fails the module lands in the installer's Failed(...) summary rather than being silently skipped.", - "guard-lib installs before the modules that depend on guardctl, so the midnight shutdown timer no longer fails on a fresh machine.", - "hosts-blocker's install.sh registers the hosts, nsswitch and resolved file-guard instances itself, so `guardctl file-guard status hosts` shows a live watcher with a canonical copy and a bind mount with no manual step after the installer.", - "Guard registration runs after the hosts file is written, so the canonical snapshot is of the post-write file.", - "Re-running the installer is a no-op that still exits 0: stop_hosts_guard/restart_hosts_guard handle the guard-lib instances, not only the legacy per-file units.", - "The security hardening suite reports 0 skipped for the screen_locker section, which had been silently skipping five checks against an extracted path." - ], - "out_of_scope": [ - "boot-repair: its BIOS/GRUB guard landed 2026-08-22 in aec2b275.", - "The installer's module-runner machinery (run_installer / ask_install); the defects are all in what it points at.", - "Retiring the legacy hosts-guard pacman hooks and systemd units, which stays in the monorepo one-shot together with --rollback/--status. A fresh machine has no legacy layer to retire." - ], - "verifier": "In a pristine vmbox guest: install_core_system.sh --all reports zero Failed entries and exits 0; a second run of the same command is a no-op that also exits 0; guardctl file-guard status hosts shows a registered instance with a bind mount; test_security_hardening.sh reports 0 skipped in the screen locker section. On the host, the same hardening suite goes from 18 passed / 1 failed / 5 skipped to 23 passed / 0 skipped before the installer runs." -} diff --git a/docs/superpowers/contracts/nftables-drift-self-check-2026-08-30.json b/docs/superpowers/contracts/nftables-drift-self-check-2026-08-30.json deleted file mode 100644 index aec9ccb5..00000000 --- a/docs/superpowers/contracts/nftables-drift-self-check-2026-08-30.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "title": "Make the nftables drift check run itself", - "objective": "setup_wireguard_ssh.sh verify already gates on the two invariants whose breach caused the 2026-08-29 and 2026-08-30 container-network outages, but nothing ever invoked it, so it reported drift only when a human thought to ask. Install systemd units that run it at every boot and daily, and make a breach a failed unit visible in `systemctl --failed` rather than a log line nobody reads.", - "acceptance_criteria": [ - "nftables-drift.service runs `setup_wireguard_ssh.sh verify` and is enabled at multi-user.target, ordered after nftables.service, docker.service and network-online.target.", - "With /etc/nftables.conf drifted, a reboot leaves the unit in `failed` and it appears in `systemctl --failed`, naming the diff and the missing docker0 accept.", - "With /etc/nftables.conf matching, a reboot leaves the unit active with both invariants OK and no false positive.", - "nftables-drift.timer is armed with Persistent=true so a machine that was off still re-checks.", - "The units are installed by `setup`, by `allow-web`/`allow-dns`, and by a new `install-verify` subcommand, so no step depends on a human remembering." - ], - "out_of_scope": [ - "Any change to the ruleset itself or to verify_nft's two invariants.", - "Notifications: a failed unit is the gate; notify-send from a root system unit is not.", - "Section 1 of the handoff (signal-bot boot resilience), which shipped separately in kuhyx/signal-bot." - ], - "verifier": "linux_configuration/features/lib/tests/test_wg_verify_units.sh under meta/scripts/shell_coverage_jail.sh; plus two real reboots in ~/src/utils/vmbox (drifted -> failed unit, clean -> active unit) and a live run on the host." -} diff --git a/docs/superpowers/contracts/nvidia-config-tests-2026-08-23.json b/docs/superpowers/contracts/nvidia-config-tests-2026-08-23.json deleted file mode 100644 index 5d41d4bf..00000000 --- a/docs/superpowers/contracts/nvidia-config-tests-2026-08-23.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "title": "Clear nvidia_config.sh and finish the fixes/lib campaign", - "objective": "Bring nvidia_config.sh to 100% measured line coverage and remove its allowlist exemption, completing the eight-lib job the campaign started with.", - "acceptance_criteria": [ - "The jail reports nvidia_config.sh at 100.00% through tests/run_all.sh at --min 100, exiting 0.", - "check_shell_coverage.sh passes on it with the allowlist line already deleted.", - "bt_audio.sh still measures 127/127 with the new test files present.", - "All four functions are covered: backup_file for present and absent files, configure_xorg including both backup paths, configure_gcc_workaround for fresh and already-configured profiles, and install_pyroveil for auto-install, existing checkout, missing dependencies, and both interactive answers.", - "configure_xorg is asserted to write RenderAccel 'true' and never 'false', since 'false' forces software rendering.", - "No write reaches /etc or a real home: /etc/X11/xorg.conf.d/20-nvidia.conf keeps its 2025 mtime and ~/pyroveil does not exist.", - "The lib stays under the 250-line cap and shellcheck reports nothing, with zero suppressions." - ], - "out_of_scope": [ - "Verifying that pyroveil actually builds; git/cmake/ninja are stubbed and only the orchestration is asserted.", - "The ~12 gated libs outside fixes/lib that already fail the gate for pre-existing reasons.", - "arch_sysctl.sh, which stays quarantined and exempt." - ], - "verifier": "timeout 900s bash meta/scripts/shell_coverage_jail.sh --timeout 600s --subject linux_configuration/scripts/single_use/fixes/lib/tests/run_all.sh --measure nvidia_config.sh --min 100 --fail-on-case-error -- \"\" (expects 76/76 = 100.00%, exit 0), then the same for bt_audio.sh as a canary, then check_shell_coverage.sh with the allowlist line removed." -} diff --git a/docs/superpowers/contracts/python-314-lint-target-2026-08-29.json b/docs/superpowers/contracts/python-314-lint-target-2026-08-29.json deleted file mode 100644 index 237c24b7..00000000 --- a/docs/superpowers/contracts/python-314-lint-target-2026-08-29.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "title": "Grade against 3.14, not just run on it", - "objective": "meta/pyproject.toml's ruff, mypy and pylint targets must name the same Python that requires-python and the CI pin already name (3.14), and the tree must be clean under those targets. Success is every lint, type and style gate passing at py314 with no suppressions added.", - "acceptance_criteria": [ - "meta/pyproject.toml has target-version = \"py314\", python_version = \"3.14\" and py-version = \"3.14\"", - "pre-commit run ruff --all-files passes", - "pre-commit run ruff-format --all-files passes", - "pre-commit run mypy --all-files passes", - "pre-commit run pylint --all-files passes", - "No noqa, type: ignore or per-file-ignores were added" - ], - "out_of_scope": [ - "Dependency version pins (handled by the separate freshness gate and its own commit)", - "CI workflow files, which already pin python-version 3.x", - "app/ and web/ subtrees, which are not Python" - ], - "verifier": "pre-commit run --all-files (ruff, ruff-format, mypy, pylint), plus the repo's CI mirror on push" -} diff --git a/docs/superpowers/contracts/split-boot-repair-2026-08-23.json b/docs/superpowers/contracts/split-boot-repair-2026-08-23.json deleted file mode 100644 index bccb3657..00000000 --- a/docs/superpowers/contracts/split-boot-repair-2026-08-23.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "title": "Close the extensionless blind spot in the length cap and split boot-repair", - "objective": "check_file_length.sh required a dot in the filename, so every extensionless executable was uncapped. boot-repair had grown to 756 lines in that gap - three times the largest capped shell file in the repo. Widen the predicate and bring the file it was hiding under the cap, without a sandbox pass that would mislead: the vmbox guest is BIOS/GRUB and this machine is UEFI.", - "acceptance_criteria": [ - "is_source_file() accepts extensionless files that begin with a shebang, and still skips extensionless data files such as i3/config.", - "Every file in the repo, including boot-repair and its new libraries, is within the 250-line cap.", - "The 42-test fixture suite passes with exactly the same count as before the split.", - "verify_shell_split.sh proves the functions moved verbatim, and any difference it reports is a deliberate change named in the evidence rather than an accident.", - "shellcheck is clean on the entry script and every library, with no suppression added.", - "No sandbox claim is made for a UEFI-critical script that a BIOS guest cannot validate.", - "pre-commit passes without --no-verify." - ], - "out_of_scope": [ - "live_test.sh, which needs root and did before this change too.", - "Restructuring boot-repair's top-level control flow to shrink it further; the report block stays inline because sourcing it broke 30 tests.", - "Other extensionless scripts, all already under the cap." - ], - "verifier": "check_file_length.sh --all reports boot-repair before the split and 'all checked files are within 250 lines' after; tests/test_boot_repair.sh reports 42 passed both before and after; verify_shell_split.sh reports 31/32 identical with clean_shadow_files as the one named difference; shellcheck exits 0 across the entry script and all four libs." -} diff --git a/docs/superpowers/contracts/suite-speedup-2026-08-23.json b/docs/superpowers/contracts/suite-speedup-2026-08-23.json deleted file mode 100644 index a8810396..00000000 --- a/docs/superpowers/contracts/suite-speedup-2026-08-23.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "title": "Make the fixes/lib suite fast without weakening it", - "objective": "Cut the suite from 110s to well under a minute so pre-push stops being dominated by it, changing only how long things wait and how the PATH shim farm is built.", - "acceptance_criteria": [ - "run_all.sh completes in under 60s and exits 0.", - "The assertion count is unchanged at 645 OK with 0 FAIL; no test is deleted, skipped or weakened.", - "The cases that depend on a tool being genuinely unfindable still pass, so the shim farm is still doing its job.", - "Every touched file stays under the 250-line cap and shellcheck-clean with zero suppressions.", - "thorium_repairs.sh's production wait is unchanged by default: THORIUM_STARTUP_WAIT defaults to the original 4." - ], - "out_of_scope": [ - "Changing what any test asserts.", - "The coverage jail's own runtime, which is dominated by kcov and runs two passes by design.", - "The pacman_hook_stall harness, which predates lib_test_core.sh." - ], - "verifier": "time bash linux_configuration/scripts/single_use/fixes/lib/tests/run_all.sh (expects exit 0 in under 60s), plus `bash run_all.sh | grep -c '^ OK'` returning 645 and `grep -c '^ FAIL'` returning 0." -} diff --git a/docs/superpowers/contracts/thorium-repairs-tests-2026-08-23.json b/docs/superpowers/contracts/thorium-repairs-tests-2026-08-23.json deleted file mode 100644 index 477f3f36..00000000 --- a/docs/superpowers/contracts/thorium-repairs-tests-2026-08-23.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "title": "Clear thorium_repairs.sh and lift the coverage gate's case timeout", - "objective": "Bring thorium_repairs.sh to 100% measured line coverage and remove its allowlist exemption, and stop the gate's 120s per-case timeout from failing libs whose coverage is actually 100%.", - "acceptance_criteria": [ - "The jail reports thorium_repairs.sh at 100.00% through tests/run_all.sh at --min 100, exiting 0.", - "check_shell_coverage.sh passes on thorium_repairs.sh with its allowlist line already deleted, so the pass cannot come from the exemption.", - "check_shell_coverage.sh passes on bt_audio.sh, which failed before this change despite measuring 127/127, because the case timed out.", - "All five profile-repair functions plus test_thorium are covered, including every dry-run arm and the sqlite3-absent path.", - "thorium_repairs.sh itself is unmodified: it already took every path from $THORIUM_CONFIG_DIR, so no production seam was needed.", - "The suite stays green and shellcheck reports nothing, with zero suppressions." - ], - "out_of_scope": [ - "Un-quarantining arch_sysctl_quarantined_cases.sh: re-adding it still breaks bt_audio's measurement even at a 600s timeout, so runtime was not its cause.", - "Changing the coverage instrument (shell_coverage_lines.py).", - "The remaining four untested libs: hosts_guard_migrate, hosts_guard_rollback, ubuntu_perf_fixes, nvidia_config." - ], - "verifier": "timeout 900s bash meta/scripts/shell_coverage_jail.sh --timeout 600s --subject linux_configuration/scripts/single_use/fixes/lib/tests/run_all.sh --measure thorium_repairs.sh --min 100 --fail-on-case-error -- \"\" (expects 75/75 = 100.00%, exit 0), then bash meta/scripts/check_shell_coverage.sh on thorium_repairs.sh and bt_audio.sh with the allowlist line removed." -} diff --git a/docs/superpowers/contracts/token-audit-batching-axis-2026-08-28.json b/docs/superpowers/contracts/token-audit-batching-axis-2026-08-28.json deleted file mode 100644 index e7bab198..00000000 --- a/docs/superpowers/contracts/token-audit-batching-axis-2026-08-28.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "title": "Measure tool-call batching per API message in token_audit", - "objective": "A per-record count of tool calls reports 1.00 calls/message regardless of actual behaviour, because the harness splits one API message into one record per tool_use block. Grouping by message.id makes the real rate (1.9%) observable, and persisting it in the snapshot lets the next weekly run adjudicate whether batching improved without any hand-rolled analysis.", - "acceptance_criteria": [ - "Batching is grouped by message.id, not by transcript record", - "The markdown report shows batched share and calls per message", - "weekly.json carries tool_messages, batched_messages and batched_calls", - "A window with no tool calls does not divide by zero", - "100% branch coverage retained and no lint suppressions introduced" - ], - "out_of_scope": [ - "Changing reconciled totals, cost weights, or the mtime-based window selection", - "Acting on the measurement (hooks/nudges live in ~/.claude, not this repo)" - ], - "verifier": "python3 -m pytest python_pkg/token_audit/tests; python3 -m coverage report (100%); pre-commit run --files ; python3 -m python_pkg.token_audit --days 7 --no-write" -} diff --git a/docs/superpowers/contracts/ubuntu-perf-fixes-tests-2026-08-23.json b/docs/superpowers/contracts/ubuntu-perf-fixes-tests-2026-08-23.json deleted file mode 100644 index 7f523b3a..00000000 --- a/docs/superpowers/contracts/ubuntu-perf-fixes-tests-2026-08-23.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "title": "Clear ubuntu_perf_fixes.sh from the shell coverage allowlist", - "objective": "Bring ubuntu_perf_fixes.sh to 100% measured line coverage and remove its allowlist exemption, leaving nvidia_config.sh as the only untested lib in fixes/lib.", - "acceptance_criteria": [ - "The jail reports ubuntu_perf_fixes.sh at 100.00% through tests/run_all.sh at --min 100, exiting 0.", - "check_shell_coverage.sh passes on it with the allowlist line already deleted.", - "bt_audio.sh still measures 127/127 with the new test files present, since adding a file to this suite has silently broken a cleared lib before.", - "All four functions are covered: the sysctl drop-in and its undo fallbacks, both NVIDIA branches (helper service and standalone fallback), earlyoom install/configure/backup, and fix_failed_sssd for none, all and exactly one failed unit.", - "The add_undo lines keep their literal /etc paths, because the undo script runs on the real system.", - "No write reaches /etc: neither /etc/sysctl.d/99-performance-tuning.conf nor /etc/default/earlyoom exists after the suite runs.", - "The lib stays under the 250-line cap and shellcheck reports nothing, with zero suppressions." - ], - "out_of_scope": [ - "nvidia_config.sh, the last untested lib, which needs its own harness.", - "The ~12 gated libs outside fixes/lib that already fail the gate for reasons predating this work.", - "arch_sysctl.sh, which stays quarantined and exempt." - ], - "verifier": "timeout 900s bash meta/scripts/shell_coverage_jail.sh --timeout 600s --subject linux_configuration/scripts/single_use/fixes/lib/tests/run_all.sh --measure ubuntu_perf_fixes.sh --min 100 --fail-on-case-error -- \"\" (expects 81/81 = 100.00%, exit 0), then the same for bt_audio.sh as a canary, then check_shell_coverage.sh with the allowlist line removed." -} diff --git a/docs/superpowers/contracts/website-admin-editor-unit-2026-08-29.json b/docs/superpowers/contracts/website-admin-editor-unit-2026-08-29.json deleted file mode 100644 index fa80219a..00000000 --- a/docs/superpowers/contracts/website-admin-editor-unit-2026-08-29.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "title": "Deploy the personal website's post editor as a loopback-only unit", - "objective": "The website repo now ships a browser post editor. Give it a home on the server: a systemd unit bound to 127.0.0.1, started by setup_personal_website.sh, with credentials generated once and no public route. Success is that after a setup run the editor answers on 127.0.0.1:4321/admin, survives a reboot, and is reachable from a laptop only through an SSH tunnel.", - "acceptance_criteria": [ - "setup_personal_website.sh builds the editor and fails if either half of the build is missing.", - "Credentials are generated once, stored as a scrypt hash in a 0600 env file, and are not rotated by a second setup run.", - "The unit binds to loopback, runs under ProtectSystem=strict with the checkout as its only writable path, and still completes a real build.", - "No Caddy snippet, no firewall opening, and no reference to the editor on the public domain.", - "The setup report prints the tunnel command and the path of the password file, never the password." - ], - "out_of_scope": [ - "Any public or Tailscale route for the editor.", - "TLS for the editor: the SSH tunnel terminates on localhost, which browsers already treat as a secure origin.", - "Changes to the website container, the shared Caddy edge, or the Gitea relocation." - ], - "verifier": "pre-commit run --files ; meta/scripts/shell_coverage_jail.sh over linux_configuration/features/lib/tests/test_website_admin.sh with --fail-on-case-error; systemd-analyze verify and a systemd-run rehearsal of the sandbox." -} diff --git a/docs/superpowers/contracts/wire-shell-coverage-ratchet-2026-08-23.json b/docs/superpowers/contracts/wire-shell-coverage-ratchet-2026-08-23.json deleted file mode 100644 index d82ba9db..00000000 --- a/docs/superpowers/contracts/wire-shell-coverage-ratchet-2026-08-23.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "title": "Wire the shell coverage ratchet into pre-commit and test the coverage tooling", - "objective": "Make the shell ratchet enforceable at commit time rather than existing but never running, and close the gap where the three libs that build the coverage jail had no tests of their own.", - "acceptance_criteria": [ - "A new shell library in a directory with no tests/run_all.sh is refused by pre-commit.", - "A shell library in a directory that has a suite, or one on the allowlist, is accepted.", - "check_shell_coverage.sh --all reports zero BLOCK entries and completes in under 5 seconds.", - "check_shell_coverage.sh emits no 'unbound variable' error on its failure path.", - "The new suite runs green standalone via bash meta/scripts/lib/tests/run_all.sh.", - "No lint suppression (noqa, shellcheck disable, per-file ignore) is added anywhere.", - "The allowlist is not grown; entries may only be removed." - ], - "out_of_scope": [ - "Restoring the jail-measured 100% bar removed in 578c4a6c.", - "Writing suites for the 8 allowlisted directories that still have none.", - "Changing shell_coverage_jail.sh, which is still used by ci_mirror.sh." - ], - "verifier": "bash meta/scripts/lib/tests/run_all.sh exits 0 with 48 assertions; pre-commit run shell-coverage-ratchet --all-files passes; creating an untested lib under mtk_root/lib and running the hook on it fails with a 'needs: .../tests/run_all.sh' message; shellcheck -x and shfmt -l are clean on every touched file." -} diff --git a/docs/superpowers/evidence/a11ydump-lock-2026-09-25.json b/docs/superpowers/evidence/a11ydump-lock-2026-09-25.json deleted file mode 100644 index 902eb5d6..00000000 --- a/docs/superpowers/evidence/a11ydump-lock-2026-09-25.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "intent": "Two sessions reading their own virtual displays at the same moment must not kill each other's dump.", - "scope": [ - "python_pkg/android_ui/_a11y_helper.py (lock), driver.py (passes the serial), tests, README" - ], - "changes": [ - "dump_display runs install + am instrument under a per-phone flock at ~/.cache/android_ui/a11ydump-.lock", - "AndroidUi passes its serial so different phones do not queue behind each other" - ], - "verification": [ - { - "command": "live two-session run on the Pixel 6a before the fix (fake owners claude:fakeA / claude:fakeB, 300 s)", - "result": "fail", - "evidence": "logcat: 'stop com.kuhy.a11ydump due to start instr' then 'Crash of app com.kuhy.a11ydump running instrumentation'; android_ui: 'a11y helper returned no tree: Process crashed'" - }, - { - "command": "same run after the fix: /tmp/two_session/run.sh, DURATION=300", - "result": "pass", - "evidence": "A 219 element taps on Calculator (display 14), B 440 dp taps + dumps on Clock (display 13), 0 failures, empty stderr; 99 samples: display 0 stayed on Signal, 14 on Calculator, 13 on Clock; all five alarms still off" - }, - { - "command": "raw adb from a Claude Bash call while claude:fakeA held pkg:__screen__", - "result": "pass", - "evidence": "`adb shell input -d 0 tap` blocked after 40 s naming claude:fakeA; `input keyevent 0` without -d blocked at once; `input -d 13 tap` refused as claude_fakeB's display" - }, - { - "command": "pytest python_pkg/android_ui python_pkg/phone_guard --cov --cov-branch", - "result": "pass", - "evidence": "153 passed; every module 100% line and branch" - } - ], - "risks": [ - "The lock is per host: sessions on another machine sharing the phone over adb are not serialised." - ], - "rollback": ["git revert the commit"] -} diff --git a/docs/superpowers/evidence/android-ui-hint-labels-2026-08-27.json b/docs/superpowers/evidence/android-ui-hint-labels-2026-08-27.json deleted file mode 100644 index d1f5fecc..00000000 --- a/docs/superpowers/evidence/android-ui-hint-labels-2026-08-27.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "intent": "Let android_ui address Flutter/Material text fields by their visible label, so a form can be filled by name instead of by screen position.", - "scope": [ - "python_pkg/android_ui/_elements.py", - "python_pkg/android_ui/tests/test_driver.py", - "python_pkg/android_ui/README.md", - "Non-goal: no change to any app under test — the gap was in this tool, not in the app" - ], - "changes": [ - "Parse the node's `hint` attribute and carry it on UiElement (default \"\", so existing constructor calls are unaffected).", - "Rank hint below text and content-desc but above resource-id in `label`, and add it to the `matches` haystack: Material clears the hint once content arrives, so a filled field still answers to its real text.", - "Keep a node that has only a hint, which the previous 'must be labelled' filter dropped.", - "Document that wrapping such a field in Semantics(label:)/MergeSemantics is a trap that empties Android's tree, and that a11y changes must be verified with uiautomator dump rather than flutter test." - ], - "verification": [ - { - "command": "python3 -m pytest python_pkg/android_ui/tests/ -q", - "result": "pass", - "evidence": "59 passed (57 before, +2 new: hint names an empty field; real text outranks the hint once typed)" - }, - { - "command": "pre-commit run --files python_pkg/android_ui/_elements.py python_pkg/android_ui/tests/test_driver.py python_pkg/android_ui/README.md", - "result": "pass", - "evidence": "no failures reported" - }, - { - "command": "python3 -m python_pkg.android_ui dump # workout_app manual-workout form, real device 23181JEGR08034", - "result": "pass", - "evidence": "Fields previously dumped as '' now read 'Start time (HH:MM)', 'End time (HH:MM)', 'Location name', 'How did you get there?', 'Cost (e.g. 40 PLN)', 'What sport/activity', 'What was done'. Nine fields then filled by name, each reporting 'typed into