@@ -42,22 +42,29 @@ go run ./scripts/check --only-freestyle
4242
4343## Command-line options
4444
45- | Option | Description |
46- | --------------------------- | ------------------------------------------------------- |
47- | ` --app NAME ` | Run checks for a specific app |
48- | ` --rust ` , ` --rust-only ` | Run only Rust checks (desktop) |
49- | ` --svelte ` , ` --svelte-only ` | Run only Svelte checks (desktop) |
50- | ` --check ID ` | Run specific checks by ID or nickname (repeatable) |
51- | ` --ci ` | Disable auto-fixing (for CI) |
52- | ` --verbose ` | Show detailed output |
53- | ` --include-slow ` | Include slow checks (excluded by default) |
54- | ` --only-slow ` | Run only slow checks |
55- | ` --fast ` | Run only the curated fast pre-commit check set |
56- | ` --only-freestyle ` | Run freestyle-compatible checks on a VM (skip the rest) |
57- | ` --prefer-freestyle ` | Run compat checks on VM + the rest locally in parallel |
58- | ` --fail-fast ` | Stop on first failure |
59- | ` --no-log ` | Disable CSV stats logging |
60- | ` -h ` , ` --help ` | Show help message |
45+ | Option | Description |
46+ | --------------------------- | ------------------------------------------------------------------ |
47+ | ` --app NAME ` | Run checks for a specific app |
48+ | ` --rust ` , ` --rust-only ` | Run only Rust checks (desktop) |
49+ | ` --svelte ` , ` --svelte-only ` | Run only Svelte checks (desktop) |
50+ | ` --check ID ` | Run specific checks by ID or nickname (repeatable) |
51+ | ` --ci ` | Disable auto-fixing (for CI) |
52+ | ` --verbose ` | Show detailed output |
53+ | ` --include-slow ` | Include slow checks (excluded by default) |
54+ | ` --only-slow ` | Run only slow checks |
55+ | ` --fast ` | Run only the curated fast pre-commit check set |
56+ | ` --only-freestyle ` | Run freestyle-compatible checks on a VM (skip the rest) |
57+ | ` --prefer-freestyle ` | Run compat checks on VM + the rest locally in parallel |
58+ | ` --fail-fast ` | Stop on first failure |
59+ | ` --no-log ` | Disable CSV stats logging |
60+ | ` --graph ` | Render the check dependency graph (weights + lanes) and exit |
61+ | ` --graph-format ` | Graph output: ` tree ` (default, colored terminal), ` mermaid ` , ` dot ` |
62+ | ` -h ` , ` --help ` | Show help message |
63+
64+ ` --graph ` honors the same selection flags (` --rust ` , ` --svelte ` , ` --app ` , ` --check ` ), so ` --graph --rust ` graphs only
65+ the Rust checks. It renders before the slow/fast/CI filters, so every lane shows with its size badge. ` mermaid ` output
66+ pastes into a Markdown ```mermaid block or https://mermaid.live ; ` dot ` pipes to Graphviz (`./scripts/check.sh --graph
67+ --graph-format dot | dot -Tpng -o checks.png`).
6168
6269## Architecture
6370
@@ -94,7 +101,8 @@ go run ./scripts/check --only-freestyle
94101| File | Purpose |
95102| --------------------- | ---------------------------------------------------------------------------------------------------- |
96103| ` main.go ` | Entry point: flag parsing, root dir discovery, check selection, pnpm gating, runner delegation |
97- | ` runner.go ` | Parallel executor: goroutine pool, dependency graph, fail-fast, live TTY status line |
104+ | ` runner.go ` | Parallel executor: CPU-weighted admission gate, dependency graph, fail-fast, live TTY status line |
105+ | ` graph.go ` | ` --graph ` renderer: dependency forest with CPU weights + size lanes (tree / mermaid / dot) |
98106| ` stats.go ` | CSV stats logging (` logCheckStats ` ): appends one row per check to ` ~/cmdr-check-log.csv ` |
99107| ` colors.go ` | ANSI color constants |
100108| ` utils.go ` | ` findRootDir() ` (walks up until ` apps/desktop/src-tauri/Cargo.toml ` is found) |
@@ -105,7 +113,17 @@ go run ./scripts/check --only-freestyle
105113## Runner-level patterns
106114
107115** Dependency graph:** Flat ` DependsOn ` slice per check. Blocked checks get ` StatusBlocked ` on dep failure and are
108- counted as failed. Dependencies not in the selected run set are treated as satisfied.
116+ counted as failed. Dependencies not in the selected run set are treated as satisfied. Visualize it with
117+ ` ./scripts/check.sh --graph ` (every check currently has ≤1 dependency, so it renders as a clean forest rooted at ` oxfmt `
118+ / ` rustfmt ` / ` gofmt ` ).
119+
120+ ** CPU-weighted admission:** Instead of a count semaphore, ` tryStartPending ` admits a check only when
121+ ` sum(running CpuWeight) + weight ≤ NumCPU ` (` runner.go ` ). A check first clears its dependencies (` canStart ` ), then the
122+ weight gate; if deps are ready but the budget is full it stays ` Pending ` and retries once a running check frees its
123+ weight. The ` usedWeight == 0 ` clause lets an over-budget check run alone rather than deadlock. This keeps two CPU-heavy
124+ checks (e.g. ` svelte-tests ` w11 + ` clippy ` -cold w8) from piling up and oversubscribing the machine, while light/long
125+ checks (` eslint-typecheck ` w2, the Docker checks) overlap freely. See the Key decision below and
126+ ` docs/notes/check-cpu-contention.md ` .
109127
110128** Slow checks:** ` IsSlow: true ` marks checks excluded by default (currently: ` rust-tests-linux ` , ` desktop-e2e-linux ` ,
111129` desktop-e2e-playwright ` ). Named ` --check ` invocations implicitly include slow checks
@@ -169,6 +187,17 @@ Use `CommandExists()` to check if a tool is installed, and auto-install if possi
169187
170188## Key decisions
171189
190+ ** Decision** : CPU-weight-aware admission instead of a count semaphore. ** Why** : The old gate allowed up to ` NumCPU `
191+ concurrent checks, but a single check (vitest, a cold cargo compile) can itself saturate every core. So the short
192+ CPU-heavy checks all launched at once and oversubscribed the machine 2-3×, which starved timing-sensitive checks — the
193+ E2E modal/popover timeouts and the 8s-cap ` file_viewer ` test flaked under ` --include-slow ` for exactly this reason. Each
194+ check now carries a ` CpuWeight ` (avg busy cores, Docker-VM-aware) and the runner only starts a check when the running
195+ weights fit the core budget. Wall-clock stays bounded by the critical path (` eslint-typecheck ` , which is long but ~ 1
196+ core, under ` --include-slow ` ; cold ` clippy ` for the default suite) while peak oversubscription drops to ~ 1×. Weights
197+ were measured by an isolation sweep (` docs/notes/check-cpu-contention.md ` ); unmeasured/fast checks default to 1. The key
198+ insight from the sweep: the longest checks (` eslint-typecheck ` , ` e2e-linux ` , ` rust-tests-linux ` ) are NOT the heaviest —
199+ they idle ~ 1 core or run entirely in the Docker VM, so they make ideal backbone fillers for the CPU-heavy short checks.
200+
172201** Decision** : Go instead of Bash for the check script. ** Why** : Cross-platform support (especially Windows), type-safe,
173202better error handling, and ability to build complex logic (parallel checks, dependency graph, colored output). Go is
174203already in the toolchain via mise.
0 commit comments