i3blocks gaming_budget: automation earner icon ⚙ (earned-time 0.3.0)
Some checks failed
dependency freshness / dependencies on newest stable (push) Has been cancelled
md-naming / markdown naming convention (push) Has been cancelled
Pre-commit checks / pre-commit (${{ matrix.group }}) (mypy, mypy) (push) Has been cancelled
Pre-commit checks / pre-commit (${{ matrix.group }}) (pylint, pylint) (push) Has been cancelled
Pre-commit checks / pre-commit (${{ matrix.group }}) (rest, ) (push) Has been cancelled
Pre-commit checks / pre-commit (${{ matrix.group }}) (shellcheck, shellcheck) (push) Has been cancelled
Python tests / test (push) Has been cancelled
repo-contract / repo contract (push) Has been cancelled
Shell tests / shell-tests (push) Has been cancelled
Shell tests / lib-tests (push) Has been cancelled
Shell tests / arch-tests (push) Has been cancelled

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Krzysztof Rudnicki 2026-10-05 15:15:50 +02:00
parent e43f1bc4fd
commit 27be07db48
4 changed files with 34 additions and 92 deletions

View File

@ -1,88 +0,0 @@
{
"intent": "Extract linux_configuration/periodic_background/hosts/ into its own public repo (github.com/kuhyx/hosts-blocker, cloned at ~/src/hosts-blocker), clearing 5 of the 46 remaining directory-depth violations without disturbing the live hosts blocker or its guards.",
"scope": [
"REMOVED: linux_configuration/periodic_background/hosts/ (15 files)",
"ADDED: linux_configuration/lib/extracted_repos.sh (shared resolver)",
"REWRITTEN callers: check_and_enable_services.sh, setup_periodic_system.sh, features/setup_dns_blocker.sh, fixes/migrate_hosts_guard_to_guard_lib.sh, install_core_system.sh, fresh-install/main.sh",
"DOCS: CLAUDE.md, INSTALLER_FIX_TASK.md, docs/shell-split-recipes.md",
"NON-GOAL: digital_wellbeing (41 violations) — separate commit",
"NON-GOAL: removing --warn from the depth hook — only valid once BOTH units are out",
"NON-GOAL: reformatting the guard plugins with shfmt"
],
"changes": [
"git filter-repo across ALL FOUR historical roots (hosts/, linux_configuration/hosts/, linux_configuration/scripts/periodic_background/hosts/, linux_configuration/periodic_background/hosts/) with matching --path-rename rules. Filtering on the current path alone would have preserved 1 commit instead of 51 — the flatten had moved the unit recently, so a naive single --path silently truncates the history to the flatten commit.",
"No source file was modified during extraction: all 15 files are byte-identical to the monorepo copies (verified with diff -r). install.sh already resolved its lib/ via BASH_SOURCE and needed no rewrite.",
"Added linux_configuration/lib/extracted_repos.sh: resolves an extracted repo's checkout via SUDO_USER's passwd entry (NOT $HOME, which is /root under sudo), supports a <NAME>_DIR override, and fails loudly with a clone instruction when absent.",
"Reused the existing REAL_HOME resolution already present in check_and_enable_services.sh for screen-locker rather than sourcing a second resolver into it.",
"fresh-install/main.sh uses \"$HOME/src/hosts-blocker/install.sh\" because it runs from ~/src/testsAndMisc/linux_configuration (a different repo) and is not sudo-re-exec'd at that point.",
"The new repo ships NO shfmt hook, deliberately: guard/plugins/*.sh are space-indented and unformatted in the monorepo too (its git hook shfmt's only STAGED files), and their installed copies under /usr/local/share/guard-lib-plugins/ are byte-identical by md5. An shfmt hook would rewrite both on first commit and break that identity."
],
"verification": [
{
"command": "git log --all --oneline -- <each of the 4 historical roots> | wc -l",
"result": "pass",
"evidence": "current path alone: 1 commit; union of all four roots: 54. Extracted repo retains 51 + 1 setup commit = 52, reaching back to 'feat: block websites uising etc hosts'."
},
{
"command": "diff -r --exclude=.git <monorepo hosts/> <extracted repo>",
"result": "pass",
"evidence": "IDENTICAL — no source file altered by the extraction."
},
{
"command": "./lib/tests/run_all.sh (in ~/src/hosts-blocker, the real clone location)",
"result": "pass",
"evidence": "test_hosts_protect.sh: 25 passed, 0 failed. All hosts install test files passed."
},
{
"command": "pre-commit run --all-files --hook-stage pre-push (in the extracted repo)",
"result": "pass",
"evidence": "shebangs/merge-conflict/end-of-file/line-ending/trailing-whitespace/shellcheck all Passed; 'hosts lib tests' Passed. No file modified by any hook."
},
{
"command": "sudo -n bash -c 'source lib/extracted_repos.sh; extracted_repo_dir hosts-blocker'",
"result": "pass",
"evidence": "HOME=/home/kuhy resolved=/home/kuhy/src/hosts-blocker — resolves the invoking user, not /root/hosts-blocker."
},
{
"command": "HOSTS_BLOCKER_DIR=/nonexistent require_extracted_repo hosts-blocker",
"result": "pass",
"evidence": "Returned 1 and printed the expected path plus a 'git clone ...' instruction — fails loudly, never a silent skip."
},
{
"command": "shellcheck on the helper + all six rewritten callers",
"result": "pass",
"evidence": "ALL CLEAN (no --severity filter, matching the repo hook)."
},
{
"command": "bash linux_configuration/tests/test_setup_dns_blocker.sh",
"result": "pass",
"evidence": "PASS: test_setup_dns_blocker.sh — the existing suite passes against the rewritten GEN path."
},
{
"command": "./meta/scripts/check_directory_depth.sh --all | head -1",
"result": "pass",
"evidence": "46 -> 41 violations; all 5 hosts entries cleared, the 41 remaining are digital_wellbeing."
},
{
"command": "diff live-state-before.txt live-state-after.txt",
"result": "pass",
"evidence": "NO CHANGE — the 8 pre-existing stale references are unchanged and none were added."
},
{
"command": "md5sum /usr/local/share/guard-lib-plugins/resolved-plugin.sh vs repo copy",
"result": "pass",
"evidence": "5f4216dc0ffdbc3feeece2346d330c56 on both — the live guards load /usr/local/share/, not a repo path, so the move does not disturb them."
}
],
"risks": [
"A machine without ~/src/hosts-blocker cloned now fails the hosts install step. That is deliberate and loud (clone instruction printed); the previous silent-skip mode is what caused five months of dead self-repair after the 2026-05-15 reorg.",
"install_core_system.sh on a genuinely fresh machine will hard-stop at the hosts module until the repo is cloned. INSTALLER_FIX_TASK.md already tracks fresh-machine installer defects and has been updated to name the new location.",
"The guard plugins remain unformatted. If an shfmt hook is ever added to hosts-blocker it will rewrite them, and the installed copies must be re-synced in the same change.",
"The live hosts blocker was NOT re-installed as part of this change; /etc/hosts and its guards are untouched and still enforced from their installed copies."
],
"rollback": [
"git revert the monorepo commit — this restores linux_configuration/periodic_background/hosts/ in full and reverts all six callers to repo-relative paths.",
"The extracted repo can be left in place; nothing in the monorepo depends on it after a revert.",
"After rollback validate: ./meta/scripts/check_directory_depth.sh --all reports 46 again, bash linux_configuration/tests/test_setup_dns_blocker.sh passes, and guardctl file-guard status resolved still names /usr/local/share/guard-lib-plugins/."
]
}

View File

@ -0,0 +1,30 @@
{
"intent": "The i3 gaming-budget block shows the new earned_time 0.3.0 automation earner (anki-guard's Automation-deck quota) as ⚙ instead of its text label.",
"scope": [
"linux_configuration/i3blocks/gaming_budget.sh",
"linux_configuration/tests/test_i3blocks_gaming_budget.sh",
"Non-goal: no change to how the budget is computed or rendered; only the icon map grows"
],
"changes": [
"Icon map gains automation: ⚙ beside workout 💪, leetcode 🧩, reading 📖, anki 🗂",
"The .rules.earners test case lists an automation earner and expects '+⚙30m' in registry order"
],
"verification": [
{
"command": "bash linux_configuration/i3blocks/gaming_budget.sh (live enforcer API, after restarting on earned-time 0.3.0)",
"result": "pass",
"evidence": "'🎮 1h19 left · 5h44/7h03 +📖1h +🗂30m +⚙30m'; /api/budget lists automation 0/1800"
},
{
"command": "bash linux_configuration/tests/test_i3blocks_gaming_budget.sh",
"result": "pass",
"evidence": "All gaming_budget checks passed, incl. '+🧩1h +📖1h +🗂30m +⚙30m +Piano1h30'"
}
],
"risks": [
"⚙ (U+2699) without a variation selector may render as text-style in some fonts; it is still one glyph wide."
],
"rollback": [
"git revert this commit; automation then shows as its label 'Automation30m'"
]
}

View File

@ -2,7 +2,7 @@
# ============================================================================
# Gaming time budget for i3blocks: time left today, used/total, and which
# bonuses (workout, LeetCode, reading, Anki, ...) are still there to be earned.
# bonuses (workout, LeetCode, reading, Anki, Automation, ...) are still there to be earned.
#
# Reads steam-backlog-enforcer's /api/budget, which already folds in the
# screen-locker, leetcode-guard and book-guard bonuses. Like screen_locker.sh
@ -74,7 +74,7 @@ rendered=$(jq -r \
# registry (.rules.earners) is the source of truth, so a new earner
# needs only an icon here; an older daemon falls back to the three
# fixed fields and the configured sizes.
| {workout: "💪", leetcode: "🧩", reading: "📖", anki: "🗂"} as $icon
| {workout: "💪", leetcode: "🧩", reading: "📖", anki: "🗂", automation: "⚙"} as $icon
| (if .rules.earners then
[.rules.earners[] | [.earned_seconds, .bonus_seconds, ($icon[.name] // .label)]]
else

View File

@ -96,12 +96,12 @@ earner() {
jq -nc --arg n "$1" --arg l "$2" --argjson e "$3" --argjson s "$4" \
'{name: $n, label: $l, earned_seconds: $e, bonus_seconds: $s}'
}
jq -n --argjson earners "[$(earner workout workout 7200 7200),$(earner leetcode LeetCode 0 3600),$(earner reading reading 0 3600),$(earner anki Anki 0 1800),$(earner piano Piano 0 5400)]" \
jq -n --argjson earners "[$(earner workout workout 7200 7200),$(earner leetcode LeetCode 0 3600),$(earner reading reading 0 3600),$(earner anki Anki 0 1800),$(earner automation Automation 0 1800),$(earner piano Piano 0 5400)]" \
'{ok: true, state_status: "ok",
today: {seconds_used: 0, budget_seconds: 14400, seconds_remaining: 14400, blocked: false},
rules: {bonuses: {}, earners: $earners}}' >"$FIXTURE"
printf '{"leetcode_bonus_seconds":0}\n' >"$CONFIG_FILE"
assert_eq '🎮 4h00 left · 0h00/4h00 +🧩1h +📖1h +🗂30m +Piano1h30' "$(line 1)" \
assert_eq '🎮 4h00 left · 0h00/4h00 +🧩1h +📖1h +🗂30m +⚙30m +Piano1h30' "$(line 1)" \
'registry order, earned omitted, sizes from the daemon not the config, label for an unmapped earner'
rm -f "$CONFIG_FILE"