diff --git a/docs/superpowers/evidence/issue-dashboard-bar-block-2026-10-02.json b/docs/superpowers/evidence/issue-dashboard-bar-block-2026-10-02.json new file mode 100644 index 00000000..05530dd4 --- /dev/null +++ b/docs/superpowers/evidence/issue-dashboard-bar-block-2026-10-02.json @@ -0,0 +1,39 @@ +{ + "intent": "Make the issue dashboard (~/src/issue-dashboard) one click away from the i3 bar: an [issue_dashboard] block shows issues to do, agents working and work ready to merge, turns red when an agent or a proof failed, and a click opens the dashboard window the same way [book_guard] opens book-guard.", + "scope": [ + "linux_configuration/i3blocks/issue_dashboard.sh - new block script", + "linux_configuration/i3blocks/config - [issue_dashboard] entry after [book_guard], interval=30", + "linux_configuration/tests/test_i3blocks_issue_dashboard.sh - behaviour test", + "Non-goal: the dashboard itself (lives in ~/src/issue-dashboard, local repo); i3/i3blocks were not reloaded on the live session." + ], + "changes": [ + "issue_dashboard.sh reads the one-line status.json the dashboard server rewrites on every refresh (~/data/issue-dashboard/status.json): '๐Ÿ“‹ ยท โš™ ยท โœ“ ', white normally, green when something is ready to merge, red with 'ยท โœ— ' when an agent or a proof failed, grey '๐Ÿ“‹ dashboard' when the file is missing, unreadable or older than 180 s (server not running). Zero forks on the hot path: one builtin read, bash regex, printf clock.", + "A click runs ~/.local/bin/issue-dashboard-desktop via setsid -f, which reuses the dashboard server or starts it detached and opens an app window.", + "~/.config/i3blocks/issue_dashboard.sh symlinked to the repo file, the way i3/install.sh links every i3blocks/* file." + ], + "verification": [ + { + "command": "bash linux_configuration/tests/test_i3blocks_issue_dashboard.sh", + "result": "pass", + "evidence": "fresh / ready / failed / stale / missing / broken / empty / click asserted; strace shows exactly one execve (bash) and no clone/fork on the hot path." + }, + { + "command": "bash linux_configuration/tests/test_i3blocks_efficiency.sh", + "result": "pass", + "evidence": "All i3blocks efficiency regression tests passed." + }, + { + "command": "~/.config/i3blocks/issue_dashboard.sh (live status.json from the real server)", + "result": "pass", + "evidence": "'๐Ÿ“‹ 5 ยท โš™ 0 ยท โœ“ 0' #FFFFFF while the server ran against Sepci0/kado-no-akari; '๐Ÿ“‹ dashboard' #6272A4 before it had ever run; BLOCK_BUTTON=1 started the launcher, which opened the (stubbed) browser with --app=http://127.0.0.1:8765/." + } + ], + "risks": [ + "The block appears only after the next i3 reload; nothing was reloaded on the live session.", + "A click with issue-dashboard-desktop missing does nothing (errors are silenced so the bar never breaks)." + ], + "rollback": [ + "git revert this commit and remove ~/.config/i3blocks/issue_dashboard.sh; i3-msg reload.", + "Confirm the bar still renders [book_guard] and the other blocks." + ] +} diff --git a/docs/superpowers/evidence/python-tests-minimal-deps-2026-08-23.json b/docs/superpowers/evidence/python-tests-minimal-deps-2026-08-23.json deleted file mode 100644 index 5456b442..00000000 --- a/docs/superpowers/evidence/python-tests-minimal-deps-2026-08-23.json +++ /dev/null @@ -1,45 +0,0 @@ -{ - "intent": "Stop the Python tests workflow installing ~62 linters and formatters it never invokes, by giving it a requirements file scoped to what the suite actually needs.", - "scope": [ - "meta/requirements-test.txt (new)", - ".github/workflows/python-tests.yml", - ".pre-commit-config.yaml (new requirements-test-sync hook)", - "Non-goal: changing pre-commit.yml, which genuinely needs every formatter" - ], - "changes": [ - "Added meta/requirements-test.txt: 17 packages, down from 79. Derived by static import analysis over python_pkg/ and linux_configuration/tests/, then verified by running the suite against only that list.", - "python-tests.yml installs the subset, caches on it, and lists it in both path filters so a change to it triggers the workflow.", - "Added a requirements-test-sync pre-commit hook reusing check_requirements_sync.py's existing --requirements flag, so a new third-party import cannot silently break CI with a ModuleNotFoundError.", - "The pytest-* plugins are included deliberately: randomly, timeout, sugar and xdist change how the suite runs even though nothing imports them." - ], - "verification": [ - { - "command": "python3.11 venv + pip install -r meta/requirements-test.txt + full pytest run with coverage", - "result": "pass", - "evidence": "2174 passed, TOTAL 100.00%, 7138 statements / 1748 branches, 0 missing -- identical to the full-requirements run" - }, - { - "command": "pip install -r meta/requirements-test.txt (timed)", - "result": "pass", - "evidence": "6.2s for 17 packages against a warm local cache; the full 79-package list took 49-55s on the CI runner" - }, - { - "command": "python3 meta/scripts/check_requirements_sync.py --package python_pkg --requirements meta/requirements-test.txt", - "result": "pass", - "evidence": "exit 0 -- every third-party import in python_pkg is declared in the subset" - }, - { - "command": "pre-commit run requirements-test-sync --all-files", - "result": "pass", - "evidence": "Passed" - } - ], - "risks": [ - "A new runtime import that is not added here would fail CI. Mitigated by the new hook, which gates on the same analysis and runs on every python_pkg .py change.", - "The subset can drift from the full list's version bounds. Both are >= ranges on the same packages, so a divergence surfaces as a resolver conflict rather than a silent mismatch." - ], - "rollback": [ - "Point the Install dependencies step and cache-dependency-path back at requirements.txt and delete the hook.", - "After rollback, confirm the install step returns to ~50s." - ] -} diff --git a/linux_configuration/i3blocks/config b/linux_configuration/i3blocks/config index 2cfc71c3..aff265a4 100644 --- a/linux_configuration/i3blocks/config +++ b/linux_configuration/i3blocks/config @@ -63,6 +63,10 @@ interval=30 command=~/.config/i3blocks/book_guard.sh interval=30 +[issue_dashboard] +command=~/.config/i3blocks/issue_dashboard.sh +interval=30 + [shutdown_countdown] command=~/.config/i3blocks/shutdown_countdown.sh interval=60 diff --git a/linux_configuration/i3blocks/issue_dashboard.sh b/linux_configuration/i3blocks/issue_dashboard.sh new file mode 100755 index 00000000..51c1fdaf --- /dev/null +++ b/linux_configuration/i3blocks/issue_dashboard.sh @@ -0,0 +1,83 @@ +#!/bin/bash + +# ============================================================================ +# issue-dashboard status for i3blocks: issues to do, agents working, work +# ready to merge. +# +# Opened the same way as book_guard.sh: a click runs issue-dashboard-desktop, +# which reuses the dashboard server if it is answering (starting it detached +# if not) and opens an app window onto it. +# +# Reads the status.json the dashboard server rewrites on every refresh (every +# poll period, ~45 s, with or without a browser open) -- no network, no gh. +# Zero forks on the hot path: one builtin read, regex matches and a printf +# clock. Never blank: a missing or stale file means the server is not +# running, and is shown as a grey "dashboard" rather than old numbers. +# ============================================================================ + +set -euo pipefail + +readonly STATUS="${ISSUE_DASHBOARD_STATUS:-$HOME/data/issue-dashboard/status.json}" +readonly LAUNCHER="${ISSUE_DASHBOARD_DESKTOP:-$HOME/.local/bin/issue-dashboard-desktop}" +# Four missed refreshes: past this the server is gone, not just slow. +readonly STALE_SECONDS=180 + +# Dracula-ish palette, matching the other blocks in this bar. +readonly RED='#FF5555' +readonly GREEN='#50FA7B' +readonly WHITE='#FFFFFF' +readonly GREY='#6272A4' + +emit() { + # full_text, short_text, color โ€” the three lines i3blocks reads. + printf '%s\n%s\n%s\n' "$1" "$2" "$3" +} + +# The only fork, and only on a click: open (or reuse) the dashboard window. +if [[ ${BLOCK_BUTTON:-0} -ne 0 ]]; then + setsid -f "$LAUNCHER" >/dev/null 2>&1 || true +fi + +# status.json is one line of flat JSON written atomically by the server. +line='' +if [[ -r $STATUS ]]; then + IFS= read -r line <"$STATUS" || true +fi + +# field : the integer value of a top-level key, or "" if absent. +field() { + if [[ $line =~ \"$1\":\ *([0-9]+) ]]; then + printf -v "$1" '%s' "${BASH_REMATCH[1]}" + else + printf -v "$1" '%s' '' + fi +} + +field todo +field working +field ready +field failed +field updated_epoch + +if [[ -z $todo || -z $working || -z $ready || -z $failed || -z $updated_epoch ]]; then + emit '๐Ÿ“‹ dashboard' '๐Ÿ“‹' "$GREY" + exit 0 +fi + +printf -v now '%(%s)T' -1 +if ((now - updated_epoch > STALE_SECONDS)); then + emit '๐Ÿ“‹ dashboard' '๐Ÿ“‹' "$GREY" + exit 0 +fi + +text="๐Ÿ“‹ ${todo} ยท โš™ ${working} ยท โœ“ ${ready}" +short="๐Ÿ“‹ ${todo}" +color=$WHITE +if ((failed > 0)); then + text="$text ยท โœ— ${failed}" + color=$RED +elif ((ready > 0)); then + color=$GREEN +fi + +emit "$text" "$short" "$color" diff --git a/linux_configuration/tests/test_i3blocks_issue_dashboard.sh b/linux_configuration/tests/test_i3blocks_issue_dashboard.sh new file mode 100755 index 00000000..af4ce4e5 --- /dev/null +++ b/linux_configuration/tests/test_i3blocks_issue_dashboard.sh @@ -0,0 +1,116 @@ +#!/bin/bash +# Behaviour tests for the i3blocks issue_dashboard block: counts, colours, +# every failure state, the click, and that the hot path forks nothing. The +# status file is a fixture and the launcher a stub, so nothing reads the real +# dashboard state or opens a window. +set -euo pipefail + +SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd) +REPO_DIR=$(cd -- "$SCRIPT_DIR/.." && pwd) +BLOCK="$REPO_DIR/i3blocks/issue_dashboard.sh" +TMP_DIR=$(mktemp -d) + +cleanup() { + rm -rf "$TMP_DIR" +} +trap cleanup EXIT + +fail() { + printf 'FAIL: %s\n' "$1" >&2 + exit 1 +} + +assert_eq() { + local expected=$1 + local actual=$2 + local context=$3 + if [[ "$expected" != "$actual" ]]; then + fail "$context (expected '$expected', actual '$actual')" + fi +} + +BIN_DIR="$TMP_DIR/bin" +mkdir -p "$BIN_DIR" +cat >"$BIN_DIR/launcher" <>"$TMP_DIR/clicks" +STUB +chmod +x "$BIN_DIR/launcher" + +STATUS="$TMP_DIR/status.json" + +# status +status() { + local epoch + printf -v epoch '%(%s)T' -1 + epoch=$((epoch - $5)) + printf '{"repo": "Sepci0/kado-no-akari", "todo": %s, "working": %s, "ready": %s, "failed": %s, "updated": "x", "updated_epoch": %s}\n' \ + "$1" "$2" "$3" "$4" "$epoch" >"$STATUS" +} + +run_block() { + ISSUE_DASHBOARD_STATUS="$STATUS" ISSUE_DASHBOARD_DESKTOP="$BIN_DIR/launcher" \ + bash "$BLOCK" +} + +line() { + sed -n "${1}p" <<<"$2" +} + +printf 'Checking issue_dashboard block states...\n' + +status 4 1 0 0 5 +out=$(run_block) +assert_eq '๐Ÿ“‹ 4 ยท โš™ 1 ยท โœ“ 0' "$(line 1 "$out")" 'fresh text' +assert_eq '๐Ÿ“‹ 4' "$(line 2 "$out")" 'short text' +assert_eq '#FFFFFF' "$(line 3 "$out")" 'nothing to merge is white' + +status 4 1 2 0 5 +out=$(run_block) +assert_eq '๐Ÿ“‹ 4 ยท โš™ 1 ยท โœ“ 2' "$(line 1 "$out")" 'ready text' +assert_eq '#50FA7B' "$(line 3 "$out")" 'something to merge is green' + +status 3 0 2 1 5 +out=$(run_block) +assert_eq '๐Ÿ“‹ 3 ยท โš™ 0 ยท โœ“ 2 ยท โœ— 1' "$(line 1 "$out")" 'failed text' +assert_eq '#FF5555' "$(line 3 "$out")" 'a failed agent is red' + +printf 'Checking failure states are never blank or stale numbers...\n' + +status 4 1 2 0 600 +out=$(run_block) +assert_eq '๐Ÿ“‹ dashboard' "$(line 1 "$out")" 'stale status' +assert_eq '#6272A4' "$(line 3 "$out")" 'stale is grey' + +rm "$STATUS" +out=$(run_block) +assert_eq '๐Ÿ“‹ dashboard' "$(line 1 "$out")" 'missing status' +assert_eq '#6272A4' "$(line 3 "$out")" 'missing is grey' + +printf '{' >"$STATUS" +assert_eq '๐Ÿ“‹ dashboard' "$(line 1 "$(run_block)")" 'broken status' +: >"$STATUS" +assert_eq '๐Ÿ“‹ dashboard' "$(line 1 "$(run_block)")" 'empty status' + +printf 'Checking the hot path forks nothing...\n' + +status 4 1 2 0 5 +if command -v strace >/dev/null 2>&1; then + ISSUE_DASHBOARD_STATUS="$STATUS" strace -f -o "$TMP_DIR/trace" -e trace=execve,clone,clone3,fork,vfork \ + bash "$BLOCK" >/dev/null + assert_eq '1' "$(grep -c 'execve(' "$TMP_DIR/trace")" 'only bash itself is exec-ed' + assert_eq '0' "$(grep -cE 'clone3?\(|fork\(' "$TMP_DIR/trace" || true)" 'no child processes' +else + printf 'strace not installed; skipping the fork count\n' +fi + +printf 'Checking a click opens the dashboard window...\n' + +BLOCK_BUTTON=1 run_block >/dev/null +for _ in 1 2 3 4 5 6 7 8 9 10; do + [[ -s "$TMP_DIR/clicks" ]] && break + sleep 0.1 +done +assert_eq 'clicked' "$(cat "$TMP_DIR/clicks" 2>/dev/null)" 'click runs the launcher' + +printf 'issue_dashboard block: all checks passed\n'