Commit Graph

8 Commits

Author SHA1 Message Date
32ea8ba949 token_audit v2: dedup, subagents, per-model USD, snapshots+ledger; repo contract; dep bumps (fixes red freshness CI)
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-10-04 16:39:42 +02:00
2a1bb60d9f Serialise the heavy ci-mirror gates when memory is tight
Some checks are pending
Pre-commit checks / pre-commit (push) Waiting to run
Python tests / test (push) Waiting to run
Shell tests / shell-tests (push) Waiting to run
Shell tests / arch-tests (push) Waiting to run
The push kept failing with "1798 passed" immediately followed by "Killed".
The suite was never the problem: it peaks at ~0.29 GiB against its own 2G
cap. ci_mirror.sh runs pre-commit --all-files and the pytest gate
concurrently, and the pre-commit tree peaks at ~1.75 GiB (mypy and pylint
repo-wide, invisible to /usr/bin/time because it measures only the parent).
The pair wants ~2 GiB at once; the pytest scope has MemorySwapMax=0, so on
a loaded machine it is the one that dies.

Concurrency is now conditional on MemAvailable. Above 4 GiB the fast path
is unchanged. The probe lives in ci_mirror_mem.sh, following the existing
ci_mirror_range/ci_mirror_shell split, which also keeps ci_mirror.sh under
the line cap.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PJrqKERAo5QUeEHWS5Asgp
2026-08-23 20:02:59 +02:00
8ab15446fd Make the push gate check the push, and make its pytest stage real
`git push` took ~130s because ci-mirror re-ran `pre-commit --all-files`
plus every lib/tests suite on every push, regardless of what changed.

The larger problem found while measuring it: ci_mirror.sh invoked
pytest_changed_packages.py with no arguments, and that script returns 0
on empty argv because it is normally a pre-commit hook fed staged paths.
The pre-push python stage has therefore been running ZERO tests and
reporting success. Behind it sat a genuinely failing test, fixed in
ff3ea99a. The stage is now passed the changed paths and actually runs.

Scope, keeping the guarantee that a red push cannot reach GitHub:

  * pre-commit runs over the push range, read from PRE_COMMIT_FROM_REF /
    PRE_COMMIT_TO_REF, which the pre-push hook already resolves from the
    refs git hands it -- the remote's real shas, so unlike origin/main
    they cannot go stale and silently empty the range.
  * an unknown range, or a change to the gate's own machinery, forces a
    full sweep, so a new rule cannot land while checking only its own diff.
  * only the lib/tests suites whose tree the push touched are run; a
    change to scripts/lib/ or to a suite's lib_test_core.sh fans out to
    all of them.
  * pre-commit and pytest run concurrently. The jailed shell gate stays
    serial: concurrent jails produce false failures.
  * an unchanged tree short-circuits on a recorded tree hash.

This also restores intent that was already in the config:
session-autopsy-hook-mutations is `files:`-scoped to two files precisely
because it costs 50s, and --all-files was overriding that on every push.

Measured, against a ~130s baseline:
  docs-only push          7.6s
  python push            25.3s   (includes a 22s stage that never ran)
  one shell suite        43.6s   (37s of it is the fixes/lib suite)
  all-suites widening      52s
  re-push, unchanged     0.005s

Proved it still gates: a deliberately broken python assertion and a
deliberately broken shell assertion were each refused with exit 1, and
the green-tree cache is not written on failure. Both probes reverted.

Split into ci_mirror_range.sh and ci_mirror_shell.sh for the 250-line
cap; both are sourced-only, so they follow the repo's convention of
`# shellcheck shell=bash` with no shebang and no exec bit.

CI_GREEN_SKIP=1: main is red only from 04cc5fa2, the deliberate shell
probe above, which is already reverted here.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PJrqKERAo5QUeEHWS5Asgp
2026-08-23 15:12:23 +02:00
170f843481 Make the jailed lib/tests gate able to fail, without breaking measurement
Some checks are pending
Pre-commit checks / pre-commit (push) Waiting to run
Shell tests / shell-tests (push) Waiting to run
Shell tests / arch-tests (push) Waiting to run
The jail recorded each case's exit code but reported only timeouts,
discarding every other non-zero status. A suite with a failing assertion
exited 0 through the push gate -- the same blind spot that let an earlier
session ship 2 failing assertions in the nc_php suite. Found by negative
control, not by reading: a deliberately failing assertion still exited 0.

Propagating that status unconditionally was wrong, and caught before landing:
the jail's own usage example passes a `bogus` case that exits non-zero to
cover an error path, so a clean 5/5 = 100.00% run started failing. That is
the mode the 105-lib campaign runs in.

So the behaviour sits behind --fail-on-case-error, default off, passed only
by ci_mirror.sh and shell-tests.yml. Verified in all four quadrants:
measurement + error case = 0, gate + error case = 1, gate + clean suite = 0,
gate + broken assertion = 1.

Both gates also pass --min 0: with no --measure the jail was measuring
run_all.sh itself against its default --min 100 and failing the push at
54.55%, a coverage verdict neither gate asked for. ci_mirror.sh now prints
the jail's output on failure, so a red gate can be diagnosed.

The added flag pushed the jail over the 250-line cap, so the in-namespace
case runner moved to lib/shell_coverage_case_runner.sh, following the
existing lib/shell_coverage_jail_setup.sh split. All four quadrants were
re-run after the extraction.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016sxxsDHWgdRhmdmzNMA6XM
2026-08-22 12:16:18 +02:00
08b58794e2 Teach both lib/tests gates to run jail-requiring suites
ci_mirror.sh and shell-tests.yml discovered every */lib/tests/run_all.sh and
invoked it bare. The features/lib suite refuses to run bare -- that guard is
what stops it rewriting the real /etc -- so the pre-push gate blocked all six
pending commits, and CI would have gone red on the same suite the moment they
landed.

A suite now DECLARES its jail requirement with a jail_args file beside its
run_all.sh, holding the --bind/--seed-* flags it needs. Both consumers branch
on the marker. The 105 still-allowlisted libs were scored on root ops and
system writes, so most future suites will need the jail too; naming the path
in two runners would drift, which is the failure ci_mirror.sh's own comments
say it exists to prevent.

Both branches fail closed: a marker present but empty is an error, not a skip.
Verified the extracted flags reproduce the independently measured
dot_resolver_install.sh 39/39 = 100.00%, and that the other five suites still
pass bare.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016sxxsDHWgdRhmdmzNMA6XM
2026-08-22 12:05:45 +02:00
1d7271f023 Phase 2: enforce the 250-line cap and stop pushing red
Two pre-commit hooks and one pre-push extension, plus the _glyph_art split
that had to land first — the cap gate cannot be wired while any file is over
it, and the lyricanki note-ascender glyph had taken _glyph_art.py to 253.

file-length-cap runs check_file_length.sh --all repo-wide rather than on
staged files, so a file that grows past the cap in some other commit is still
caught. Proven to block, not assumed: with a deliberate 261-line file staged
the hook exits 1 and names it; with the file removed it passes.

ci-baseline-green is the user's option (a): refuse to build on a main that is
already red. It fails closed on a definitively failing baseline and warns-and-
passes on anything it cannot determine — offline, no gh, unauthenticated, no
settled runs — each call capped at 5s. Blocking every commit on network
reachability buys nothing when (b) is the real per-change guarantee.
CI_GREEN_SKIP=1 is the escape for a fix-forward commit. python-tests.yml is
excluded on purpose: it is path-filtered, so a shell-only commit never
triggers it and its "latest run" would produce false reds.

This hook already earned its place. Run against a red main earlier in this
session it refused with "Shell tests (failure)", which is how the broken
SETUP_SOURCES was found instead of being pushed over.

ci_mirror gains the shell-tests workflow's side-effect-free half — every
lib/tests/run_all.sh plus the workflow's own test list, the user's option (b).
That list is parsed out of shell-tests.yml rather than copied: a hardcoded
duplicate drifts the moment someone adds a test, and a mirror that quietly
checks less than CI is worse than no mirror because it reports safe. The
Arch-container half stays unmirrored — it wants root and an Arch userland.

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

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

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

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

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

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

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

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018PMRRVVkeRyrSaaqueMZKG
2026-07-11 18:06:17 +02:00