~/src/daily-limits' user timer publishes today's limits for its phone app
and answers its requests, with a Firebase session of its own.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AcxgxMaQHMSGWzLRpNSkuP
From LADDER_FROM (2026-10-10) shutdown is read down from sleep: ceiling =
WAKE_MINUTES (07:00) - 8h = 23:00, floor = ceiling - every earner's rung
(workout 110, LeetCode 50, reading 30, Anki 25, Automation 25) = 19:00.
A second workout earns nothing more. Earlier days resolve exactly as in
0.3.0 (frozen 23:00 ceiling); gaming is unchanged for every input.
WORKOUT gets a signed ledger (~/.local/share/workout_locker/ledger.json,
written by screen-locker) and workout_match; a rest_day row counts only if
declared before its day began. New API: first_credit_at, credit_units,
day_window, shutdown_minutes_for, extra_shutdown_minutes_for,
shutdown_ceiling_for, on_ladder, LADDER/Rung, LADDER_FROM, WAKE_MINUTES.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QrjUTo1yhgAsUUasdw4bpG
subosito/flutter-action's cache is the whole SDK, 1.6 GB: ~38 s to restore
on every Flutter job even on a hit, which alone kept them over a minute.
This composite action caches a depth-1 clone of the release tag with only
the host and web artifacts (393 MB zstd'd); verified locally that test,
analyze --fatal-infos and build web run on it without lazy downloads.
Release APK builds keep subosito, which they need for the Android engines.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FBbkN4ZB4aUPJPzR2prRan
screen-locker and leetcode-guard each carried a near-identical copy of the
reader for wake-alarm's signed morning_session.json; book-guard is about to
need it too. The policy stays in wake-alarm -- this only answers "signed,
dated today, now < exempt_until". Path and retry budget are arguments so
each consumer's test redirect keeps working.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FBbkN4ZB4aUPJPzR2prRan
blocker split blocked_by on the first "@", so "transitive:@scope/name@1.2.3"
read the package name as "" and left "scope/name@1.2.3" stuck to the
version -- the format check then rejected the entry outright. rpartition
splits on the last "@" instead, which is always the version separator
even when the package name carries its own leading "@".
Found adding a typescript-eslint entry (blocked by its own
@typescript-eslint/scope-manager not having published 8.70.1 yet) in
signal-bot.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Zg7ULrGyB1gMcKC1mkbLb
Two forks per path (`git ls-files --error-unmatch`, `grep -qiE <<<`) made
the gate a 5 s step on a 2053-file repo. The tracked set now comes from a
single `git ls-files -z -- "$@"` and the extension match is bash's own
`=~` on the lower-cased path (same case-insensitive semantics as `grep -i`).
0.2 s on the same tree; verdicts unchanged (blocked / allowlisted /
untracked-ignored checked on a throwaway repo).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016P4KZw1E2sXJjYqfcZWf1x
Three findings from proving the JitPack fix on the TachiyomiSY gate:
- The first repository with a stable answer used to win. Central still
carries com.github.ben-manes:gradle-versions-plugin 0.11.1 (2016) while
the plugin portal has 0.54.0, so a 0.51.0 pin graded "current". Every
repository is now asked and the newest stable across the pooled lists is
the answer.
- ~900 lookups, eight at a time, each resolving the registry host (A+AAAA)
per connection: the LAN router's forwarder started dropping queries for
every client on the LAN and the gate degraded itself. socket.getaddrinfo
is memoised per (host, port, hints) for the run, and the per-host probe
runs under a lock so eight workers make one probe, not eight.
- A resolver EAI_AGAIN ("temporary failure") during the probe marked the
registry unreachable for the whole run. The probe retries it
HTTP_ATTEMPTS times; a permanent failure still fails immediately.
Both retry loops lost their unreachable `return` sentinel (http.py is at
100% branch now); the resolver tests live in test_http_resolver.py to keep
test_http.py under the 250-line cap.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016P4KZw1E2sXJjYqfcZWf1x
Since ~2026-09-14 https://jitpack.io/.../maven-metadata.xml answers HTTP 401
"No access token" (artifact POM/AAR downloads still 200), which turned every
com.github.* coordinate into "undeterminable" (exit 3) in --strict CI.
JitPack is dropped from MAVEN_REPOS. A com.github.<owner>:<repo> name that no
HTTP repository knows is now answered from the GitHub repo's tags via
`git ls-remote --tags --refs` -- JitPack's version IS the tag name, so the
tags are compared as they are (v prefix included). The tag list goes through
the same stable-first selector as maven-metadata, so a newer pre-release tag
does not win. Not an allowlist entry, not a token.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016P4KZw1E2sXJjYqfcZWf1x
The packageManager field was bumped after the last install, so CI's
--frozen-lockfile refused it (ERR_PNPM_FROZEN_LOCKFILE_WITH_OUTDATED_LOCKFILE).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DnAYyNH1Sm4iJNidfYbrZJ
The ruff bump surfaced one ANN001 in test_window.py. ts-core / web-ui /
ts-config workflows gain workflow_dispatch so a workflow-only change (like the
last commit, which touched no ts_core/** path and therefore never ran) can be
re-verified on demand.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DnAYyNH1Sm4iJNidfYbrZJ
ts_core and web_ui depend on `link:../ts_config`. pnpm links the directory
and installs nothing inside it, and a flat config imports its plugins from
the preset's own node_modules, so CI died on "Cannot find package
'@eslint/js'" while the local tree (where ts_config had been installed by
hand) passed. Both workflows now `pnpm install` in ts_config first, and
trigger on ts_config/** too so a preset change re-verifies its consumers.
The same push turned the dependency-freshness job red on five pins that had
aged underneath it (ruff 0.16.8 in four requirements.txt,
flutter_secure_storage 11.2.0 in crdt_sync_flutter); bumped, pubspec.lock is
ignored in that package so nothing else moves.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DnAYyNH1Sm4iJNidfYbrZJ
The two shared packages are the first repos on the preset (link:../ts_config
inside the monorepo; consumers outside use the git tag). Each eslint.config.js
is now the defineConfig() call plus one override with its reason: a public
API name (`nextChance`, `fuzzyMatch`) the boolean-prefix rule would rename,
which is a breaking change for the three repos importing it.
Everything else the preset flagged is fixed in code, not disabled:
key order (perfectionist), hex casing, sort compare functions in tests,
Iterator#map().toArray(), explicit returns on multiline arrows.
web_ui: react-refresh/only-export-components was a real finding --
range-slider.tsx exported `steppedValue` next to the component, which breaks
Vite HMR for the module. The keyboard contract moves to stepped-value.ts as
`steppedValue` + a new pure `keyTarget(key, values, current)`, which also
resolves unicorn/no-late-event-control: the handler now decides the target
first and calls preventDefault() only when it owns the key. Its tests move
with it (the 250-line gate caught the combined file). Minor bump for the new
export. dist/ rebuilt in both packages.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DnAYyNH1Sm4iJNidfYbrZJ
Surfaced by making ts_core and web_ui the first consumers:
- base({ project }) uses an explicit tsconfig list instead of the project
service. Both packages exclude tests from tsconfig.json so they never land
in dist/, and widen the include in tsconfig.lint.json for lint only; the
project service would have reported every test file as "not found".
- unicorn/no-barrel-files is off for src/index.ts: a package entry point IS
a barrel, that is its job. Stays on everywhere else.
- unicorn/no-keyword-prefix is off in the React layer: `className` is
React's prop name and fires on every styled element.
- pnpm-workspace.yaml: allowBuilds for unrs-resolver set to false explicitly
(pnpm 12 had written a placeholder); the resolver's prebuilt binding is
used, no install script runs.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DnAYyNH1Sm4iJNidfYbrZJ
Evil Martians' "ten anti-AI-slop moves" as a package: strict tsconfig base
(noUncheckedIndexedAccess, exactOptionalPropertyTypes), type-checked
typescript-eslint strict+stylistic, unicorn/sonarjs/perfectionist, behaviour
rules for tests (expect-expect, no-focused-tests), React (hooks, @eslint-react,
jsx-a11y strict, you-might-not-need-an-effect), Playwright, and layer
boundaries via eslint-plugin-boundaries 7 with the article's default policies.
Knip / jscpd / Stryker bases; Stryker carries no break threshold on purpose.
Plugins are dependencies, not devDependencies: a flat config imports them from
this package's own node_modules, so a consumer installing from the git tag
gets them or the lint cannot run. No build step, so the tag needs no prepare.
test/fixture is a mini repo with one planted violation per rule family;
test/expected.js names the rule that must catch each, and the suite runs the
real CLI over it (in a child process -- v8 coverage merges natively-loaded
and transformed copies of the same module into "uncovered"). 35 tests, 100%
branch coverage on the factories.
eslint/fast.js is the type-free subset for a per-edit hook: 0.52 s cold on
one file, measured against 1.28 s for base() without types -- importing a
plugin is the cost, so it loads none, nor base.js which imports them.
Probed under ESLint 10: jsx-a11y 6.10.2 (peer says <=9) and boundaries 7.2.0
both run; the fixture pins that.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DnAYyNH1Sm4iJNidfYbrZJ
QEMU user-mode networking (libslirp 4.9.4 / qemu 11.1.1) freezes 2-4 of 5
concurrent 60 MB streams at ~10 MB until they time out, while one stream
does 45 MB/s. pacman's ParallelDownloads = 5 hit this on every guest
upgrade: a 90-package -Syu crawled at "500 KB/s" for minutes and then
finished in 12 s once set to 1. Window scaling and rmem do not help; a
tap/bridge NIC would but needs root, which vmbox avoids by design.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SS8M5wk88nJTXxDWz61KKk
The golden image logs in with agetty+startx and has no RGB controllers,
no NVIDIA card and no sound card, so the night-lockdown scripts — which
mask/stop lightdm.service and getty@.service and shell out to openrgb,
nvidia-smi and amixer — had nothing real to act on in a sandbox and
were being "verified" on the host instead.
- `vm lightdm <name>`: installs lightdm + lightdm-gtk-greeter into the
live overlay (pacman -Syu, not -S: the overlay's package database is
the image's and mirrors have rotated those versions out), configures
autologin -> i3 exactly like the host, turns tty1 back into a plain
getty, reboots the guest and proves lightdm.service is active with an
Xorg it owns. Never rebuilds the base: `vm build --force` re-seals a
new checksum while every existing sandbox still backs onto the old
file's name, which is silent qcow2 corruption.
- `vm shim <name> <tool> [exit[:N]|hang|sleep:S]`: a fake tool ahead of
the real one on PATH, every call logged with an epoch to
/var/log/vmbox-shim/<tool>.log so the host can assert what ran and in
what order. `hang` is the openrgb 1.0-2 that never returned on
2026-09-12 and defeated the curfew.
- The DISPLAY/XAUTHORITY resolver `vm run` sources becomes its own file,
guest/vmbox-x11.sh, installed by the image build and re-installed by
the lightdm switch; it now tests readability, not existence, and tries
~/.Xauthority before the startx serverauth glob, because lightdm's
Xorg -auth file is root-only.
- README: the worked example uses MIDNIGHT_SHUTDOWN_CONFIRM=y (the
installer refuses to run unattended now; `echo y |` no longer works),
and the "i2c RGB stays on the host" line is gone.
- bats: four host-side tests for shim.sh (exit, exit:N, hang, refusals).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015mLnUeoJ3mUwGRTowTUH6d
Dependency-freshness CI went red on the gatelock 0.8.2 push because
@types/node moved under it; bump both pins and lockfiles.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BxkZDHDmoVSxUT4w72xSiE
The 60 s deadline was the 2026-09-13 leetcode-guard miss with a smaller
number: a Persistent=true catch-up starts one second into a boot, before X
exists, and a bounded wait that expires exits the unit while systemd has
already stamped the slot as fired. No X server means the user cannot use
the machine either, so an unbounded wait is not a bypass. The wait now
re-states itself at WARNING every five minutes so a long wait never reads
as a healthy, silent unit. `timeout_s` stays as an opt-in for callers that
want a bound.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BxkZDHDmoVSxUT4w72xSiE
The palette-drift check parsed LockConfig's colour defaults; they live
on LockPalette in _theme.py since 0.8.0, so it read zero tokens and
failed closed as designed. The two new LockWindow handles had no test
reaching them, which CI's 100% bar caught and the local run did not.
gatelock/prompts/TODO-pylint-to-ten.md described this job with the
older design (raise the score, leave the gate); the gate is at 10.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HHCbkJWXHjtTUaqvnHRAqA
leetcode-guard's study mode has to stop the re-assertion loop and the
output-change watcher before it releases the grab, and could only reach
them as LockWindow._recovery / _detector -- attributes 0.8.0 folded into
the arming bundle, which broke that documented private dependency.
They are public read-only properties now, next to `surfaces`.
_window no longer re-exports the LockConfig family; it was the one
thing keeping it over the 250-line cap, and every importer now takes
those names from gatelock._config (or the package) directly.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HHCbkJWXHjtTUaqvnHRAqA
LockConfig was one 35-field record: the lock's behaviour, the grab
policy, nine colours, a type scale and a spacing scale, flat. It is
now the seven behavioural knobs plus four nested records --
GrabPolicy(kind, disable_vt, retry_ms, log_every, preempt_weaker_holder),
LockPalette, LockTypography and LockSpacing -- each overridden as a
unit. Breaking for every constructor that passed grab=/disable_vt=/
grab_retry_ms=/preempt_weaker_holder= or a colour, and for every read
of config.<colour>; hence the minor bump. screen-locker, leetcode-guard
and diet-guard follow in their own commits.
The two fail-open guards in the recovery loop carry per-line
suppressions: catching everything is the property they exist for, and
the check stays live for the next careless except. That was the last
finding, so the gate now says 10.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HHCbkJWXHjtTUaqvnHRAqA
The three import-error findings were the hook not having crdt-sync in
its own venv; it is now pinned there as in pyproject.toml. The one
source finding is the background sync's documented fail-open net, which
must catch everything by design, so it carries the one suppression.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HHCbkJWXHjtTUaqvnHRAqA
Same config move as crdt-sync (ignore-paths, init-hook, the three
implicit-booleaness disables the reference repos carry). The gate stays
at 8.0 for now: LockConfig's 35-field record and the two fail-open
guards in the recovery loop are still open.
Everything else pylint had left on source is gone. The optional
python-xlib dependency is loaded through one importlib helper instead
of two function-scope import statements, keeping the "not installed"
arm reachable for branch coverage; RecoveryLoop and LockWindow hold
their collaborator bundles rather than unpacking them into a second
set of attributes; the claim types are imported from _claims, where
they are defined, so _arbiter no longer needs an export list that
mirrored the package's; runtime imports sit above TYPE_CHECKING
blocks; the RandR Protocol stubs say which Xlib call they mirror.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HHCbkJWXHjtTUaqvnHRAqA
settings.gradle.kts can declare any number of catalogs via from(files());
TachiyomiSY has mihon.versions.toml and sy.versions.toml beside libs.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Yc4TiPjhbQLfH35QBPfrbP
The gate had no way to read a Gradle build, which left the two Kotlin
forks (tachiyomisy, syncyomi-src) outside the fleet-wide rule.
- parsers/gradle.py reads gradle/libs.versions.toml ([libraries] and
[plugins] in every shape: inline string, module + version.ref,
group/name/version, rich {strictly|require|prefer} tables) into
`maven:<group>:<artifact>` deps, pointing the fix at the [versions]
line when a ref is used; BOM-managed entries without a version are
skipped. gradle-wrapper.properties becomes `toolchain:gradle`.
- registries/maven.py asks Google Maven, Central and the plugin portal
in that order, reading the full <versions> list -- AndroidX writes
alphas into <latest>. Plugins resolve through their marker artifact.
- registries/toolchain.gradle_latest reads services.gradle.org.
- http.get_text shares the transport with get_json (XML bodies).
- Templates: the pre-commit `files:` and the CI cache key know the
catalog. 100% branch on every new file; fixtures only.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Yc4TiPjhbQLfH35QBPfrbP