Commit Graph

19 Commits

Author SHA1 Message Date
Will Pfleger
cd0d33f085 fix(hooks): scope pre-push lanes to branch merge-base diff (#6423)
Lefthook selected pre-push lanes from the file set `git diff --name-only
HEAD @{push}` returns, which is overinclusive two different ways:

- **Rebase/merge poisoning.** `@{push}` is a two-dot diff against the
branch's last-pushed tip. After a rebase onto newer main — or the `git
merge origin/main` that `check-branch-skew.sh` itself instructs agents
to run — that diff includes everything `main` changed since the last
push, so `mobile-checks`, the `desktop-*` lanes, and `rust-tests` fire
on branches that never touched those trees (a recent relay-only push
discovered 266 files, 26 of them `mobile/`).
- **Brand-new branch in a worktree.** When `@{push}` doesn't resolve,
lefthook reads `$GIT_DIR/refs/remotes/origin/HEAD` — which doesn't exist
in a linked worktree's git-dir — then falls back to the
alphabetically-first `HEAD ->` remote. In a clone with a stale
third-party remote sorted before `origin`, that diffs against a frozen
tip and runs the full suite. All agent work happens in worktrees.

## Fix

Add `files: git diff --name-only origin/main...HEAD` to the six globbed
pre-push lanes (`rust-tests`, `desktop-check`, `desktop-typecheck`,
`desktop-test`, `desktop-tauri-checks`, `mobile-checks`). The three-dot
form is the branch's merge-base diff against `origin/main` — the same
base resolution `scripts/check-file-sizes-core.mjs` (`resolveBaseRef`)
and CI's dorny/paths-filter already use. A lane now fires only when this
branch actually changed a file it covers; `main`-side changes never
re-trigger it, and a new branch diffs against `origin/main` (empty diff
→ all globbed lanes skip) rather than a stale remote. `branch-skew` and
`file-size-check` stay unfiltered by design.

Also pin hook dispatch to the Hermit-managed lefthook. The generated
`.git/hooks/*` dispatchers prefer `$LEFTHOOK_BIN`, then a `lefthook` on
`PATH` — so a Homebrew lefthook shadowed the pinned 2.1.3. A `rc:
bin/.lefthookrc` (a lefthook config key `lefthook install` bakes into
every dispatcher) exports `LEFTHOOK_BIN` to `bin/lefthook`, so a push
runs the pinned version regardless of `PATH`. `just hooks` already runs
`lefthook install`, so re-running it picks up the `rc` line — no recipe
change needed.

`AGENTS.md` documents that lanes are scoped by merge-base diff and that
agent shells should lead `PATH` with `./bin` so pinned tools win.

## Verification

Reproduced both bugs in a scratch repo mirroring the worktree +
stale-remote topology, with lanes stubbed to echo on selection:

- **Bug 1:** relay-only branch, `git merge origin/main` (main carried
`mobile/` + `desktop/` changes). Without the fix, `desktop-check` and
`mobile-checks` fired; with it, three-dot diff = only the relay file,
all globbed lanes skip.
- **Bug 2:** brand-new branch, no upstream, in a linked worktree with an
alphabetically-first stale remote. Confirmed `@{push}` is `fatal`,
`refs/remotes/origin/HEAD` is absent from the worktree git-dir, and the
stale remote's `HEAD ->` sorts first. Without the fix it fired lanes off
the stale tip; with it, lanes scope to `origin/main` merge-base and
skip.
- **Positive controls:** a `mobile/**` commit still fires
`mobile-checks`; a `crates/**` commit still fires `rust-tests` +
`desktop-tauri-checks`.
- **Dispatch:** with Homebrew lefthook 2.1.10 on `PATH`, running the
real generated `.git/hooks/pre-push` under `LEFTHOOK_VERBOSE=1` shows it
sourcing `bin/.lefthookrc`, exporting `LEFTHOOK_BIN`, and running
`lefthook v2.1.3`.
- This PR's own push (a brand-new branch in a linked worktree — the bug
2 case) ran only `branch-skew` and `file-size-check`; no
mobile/desktop/rust lanes fired, since the branch touches only
`lefthook.yml`, `AGENTS.md`, and `bin/.lefthookrc`.

---------

Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
Co-authored-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
2026-08-20 15:55:00 -04:00
Will Pfleger
81567b76a5 chore: serialize mobile pre-push checks (#6322)
Serialize the mobile pre-push analysis and test commands into one lane,
mirroring the existing `desktop-tauri-checks` pattern.

The shared Flutter state under `mobile/` (`.dart_tool`, shader bundle,
and `pubspec.lock`) must not be driven by parallel hook processes. The
existing `mobile/**` path filter is unchanged; `mobile-check` now runs
before `mobile-test` in the same lane.

- Replace the parallel `mobile-check` and `mobile-test` commands with
`mobile-checks`.
- Keep the commands serial with `just mobile-check && just mobile-test`.

Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: Alia <d32955ad69077062930cc46cfe2df30ca9aaf6f8e76422681265e9e9af704d78@buzz.block.builderlab.xyz>
2026-08-19 13:10:03 -07:00
Wes
b74700daaf chore(hooks): keep mobile analysis out of pre-commit (#6236)
## Summary

- run only Dart formatting for mobile changes during pre-commit
- move Flutter static analysis to the path-scoped pre-push graph
- fix case-sensitive `Justfile` triggers for the Rust and Tauri pre-push
gates
- skip whole-tree desktop/web formatting for lockfile-only commits while
retaining every lockfile-triggered pre-push check

No test suite was removed or narrowed. Cargo formatting remains
workspace-scoped; frontend and mobile source changes still select their
existing formatters.

## Benchmark

Warm isolated timings on an M2 Max:

- `dart format .`: 1.92–2.23s
- `flutter analyze`: 5.58–7.51s
- old forced full pre-commit: 10.16–18.40s
- current forced full pre-commit at `f18d9b5`: 5.42–7.09s, median 6.29s
(45% lower)
- current forced full pre-push at `f18d9b5`: 2m45s, effectively
unchanged from the prior warm 2m41s run
- lockfile-only pre-commit after the follow-up: 0.18–0.19s across three
runs

The new mobile analysis lane finishes before the existing mobile test
lane, so it added no observed full-push wall time.

## Validation

At pushed head `79626be60d89ba34e0fe136f254cc38cf1f1c2b8`:

- `lefthook validate`
- isolated temporary-repository selection test:
  - `Justfile` selects both Rust and Tauri pre-push gates
  - `pnpm-lock.yaml` selects neither mutating frontend formatter
  - desktop/web source files still select their formatter
- lockfile-only pre-commit: 0.19s, 0.19s, 0.18s
- pre-push hook passed on the exact pushed head

Earlier full-cycle validation at
`f18d9b5802f11543e8afbef3cd54c5928817e32b`:

- forced full pre-commit: 5.42s, 6.29s, 7.09s
- forced full pre-push: 2m45s; all lanes passed
- mobile tests: 1,465 passed

---------

Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
2026-08-18 12:56:06 -07:00
Wes
6d45f98665 ci: make file-size policy a first-class gate (#6187)
## Summary

- make the differential file-size ratchet a first-class repository gate
- run the same unfiltered gate from pre-push, `just check`, and CI
- remove hidden file-size coupling from Desktop, Web, and Mobile lint
commands
- isolate ratchet Git subprocesses from hook-exported repository state

## Why

The Desktop ratchet grew to govern `desktop/src-tauri/crates/**`, but
the pre-push `desktop-check` command remained path-filtered to non-Tauri
files. That contract drift allowed a Tauri Rust file-size regression
through local validation.

The ratchet already computes its own merge-base diff, so duplicating
governed paths in Lefthook and CI adds drift risk without meaningful
runtime savings. One root gate owns the policy now.

## Testing

- `just file-size-check`
- oversized untracked probe under `desktop/src-tauri/crates/**` fails
with the 1,000-line ceiling
- file-size core tests with hook-style `GIT_DIR` / `GIT_WORK_TREE`
environment
- `lefthook dump` confirms the unfiltered pre-push command
- mandatory pre-push suite passed on
`3217003db10c84d8a0c5f636ec4c92936777a002`
- `git diff --check`

Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
2026-08-18 06:36:25 -07:00
Will Pfleger
c777d4fb9a chore(hooks): run desktop typecheck in pre-push (#5110)
The local pre-push gate ran biome (`desktop-check`) and node:test
(`desktop-test`) for desktop changes but never `tsc`, so TypeScript
errors surface no earlier than CI's `desktop-core` job (`just
desktop-build` = `tsc && vite build`). A branch with type errors passes
every local hook today.

This adds a `desktop-typecheck` pre-push command running `just
desktop-typecheck` (`tsc --noEmit`) with the same glob/exclude as
`desktop-check`, and updates the hook documentation in `AGENTS.md`. CI
is unchanged — it already typechecks via `desktop-build`.

Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
2026-08-06 16:57:12 -04:00
Wes
9752b816a9 Serialize Tauri pre-push checks (#3567)
## Summary

- combine Desktop Tauri clippy and tests into one pre-push command
- run clippy first, then tests
- keep unrelated pre-push commands parallel

## Why

PR #3555 added clippy as a separate command while the pre-push group
uses `parallel: true`. That can start clippy and tests simultaneously
against the same Cargo target directory, leaving one command waiting on
Cargo's build lock and making pushes appear stalled.

Serializing only these two Cargo-heavy checks avoids lock contention
while retaining the CI-equivalent clippy command and existing test
coverage.

## Validation

- `lefthook validate`
- forced `desktop-tauri-checks` through Lefthook with an instrumented
`just`; observed `desktop-tauri-clippy` followed by `desktop-tauri-test`

Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
2026-07-29 08:52:50 -07:00
Wes
51bb97d2be Run Tauri clippy in pre-push (#3555)
## Summary

- run Desktop Tauri clippy from pre-push for every path that can affect
the Tauri crate
- reuse `just desktop-tauri-clippy`, keeping the local command identical
to Desktop Core CI
- leave the existing Tauri test hook unchanged

## Why

PR #3553 exposed a hook gap: `cargo test` allowed an unused-import
warning that CI's `clippy -D warnings` correctly rejected. Running the
same recipe before push catches that class of failure locally without
duplicating CI flags in Lefthook.

## Validation

- `lefthook run pre-push --command desktop-tauri-clippy --force`
- confirmed it invokes `cargo clippy --manifest-path
desktop/src-tauri/Cargo.toml --all-targets -- -D warnings`
- command passed

Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
2026-07-29 08:20:49 -07:00
Will Pfleger
dc1646fcb9 docs: document required DCO sign-off and add commit-msg sign-off hook (#2993)
`DCO Check` is a required status check on this repo and the top failing
check on open contributor PRs, but nothing documented it and nothing
surfaced it locally — a missing `Signed-off-by` trailer only showed up
as a red check after the PR was already open.

## `lefthook.yml`

New `commit-msg` hook that appends the `Signed-off-by` trailer:

```yaml
commit-msg:
  commands:
    signoff:
      run: 'git interpret-trailers --if-exists doNothing --trailer "Signed-off-by: $(git var GIT_COMMITTER_IDENT | sed ''s/ [0-9]* [+-][0-9]*$//'')" --in-place {1}'
```

`GIT_COMMITTER_IDENT` is the identity that performed the commit, which
is what a DCO sign-off certifies and what native `git commit -s` uses.
Committing someone else's work with `--author` or `git commit -C`
therefore signs off as you, not as the original author.

`--if-exists doNothing` makes it idempotent: `git commit -s` still
yields exactly one trailer, and an existing sign-off from a different
signer is preserved rather than supplemented. An empty commit message
still aborts — the hook does not turn one into a commit body containing
only a trailer.

Git runs `commit-msg` for `git commit` and `git merge` only. Other flows
bypass it and need their own sign-off flag — `git rebase --signoff`,
`git cherry-pick -s`. Note `-s` is `--strategy` on `git rebase`, so only
the long flag works there. The header comment and both docs state the
scope rather than promising blanket coverage. Installed by `just hooks`;
`commit-msg` carries no `glob` because it rewrites the message, not
files.

## `CONTRIBUTING.md`

`Before You Open a PR` gains a paragraph on sign-off: commit with `git
commit -s`, what the trailer certifies, that the required `DCO Check`
blocks merge without it, `git rebase --signoff main` to repair
already-pushed commits, and what the hook does and does not cover.

`CI Gate` gains one sentence pointing at `just fix-all` for
formatting-only failures.

## `AGENTS.md`

`Quality Gates` gains the same requirement framed for agents, including
the sequencer caveat and the reminder to include `-s` in
programmatically built commit commands.

## Related issue
none found

---------

Signed-off-by: npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
2026-07-26 13:43:06 -04:00
Will Pfleger
692c56d227 chore(lefthook): add CI-style path filtering to hooks (#2211)
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@sprout-oss.stage.blox.sqprod.co>
2026-07-21 03:15:54 -04:00
Wes
4ddb61f7a8 Add pre-push branch-skew guard and changed-spec e2e recipe (#1940)
Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Brain <21994759fc7a6fa6b965551d35cfd7897d262f2495467f2d78694ddcfa6a5c7e@sprout-oss.stage.blox.sqprod.co>
2026-07-15 21:43:32 -07:00
Wes
3e8dae7ff3 fix(desktop): simplify sign out settings row (#1903)
Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Pinky <44b8e82baa6e0e254e0208d68f335c283c94e7b78dd1fa10d5a49d3f13dd0435@sprout-oss.stage.blox.sqprod.co>
2026-07-15 15:26:01 +00:00
Will Pfleger
07efae7b5b perf(ci): speed up PR CI wall clock and local dev builds (#1028) 2026-06-16 12:44:13 -04:00
Will Pfleger
3fbee555f0 chore(hooks): standardize check/fix convention with auto-fix pre-commit (#776) 2026-05-28 17:42:35 -04:00
Wes
30654e95f6 Polish desktop Pulse and Home views (#764)
Signed-off-by: Wes <wesbillman@users.noreply.github.com>
2026-05-28 08:17:02 -07:00
Will Pfleger
11905d1fbc chore: activate lefthook hooks and add fmt-all, mobile-fmt recipes (#751)
## Summary

- Activate lefthook pre-commit and pre-push hooks that have existed in `lefthook.yml` since the initial desktop app but were never wired up.
- Add `just hooks` recipe: sets `core.hooksPath = .hooks` and runs `lefthook install --force` to generate hook scripts.
- Wire hook installation into `scripts/dev-setup.sh` so hooks activate automatically on `just setup`.
- Enable `parallel: true` for pre-commit hooks (pre-push already had it) — all 5 format/lint checks run simultaneously.
- Add `just mobile-fmt` recipe (`dart format .`) and `just fmt-all` recipe (Rust root + Tauri Rust + Dart) as one-shot formatters.
- `.hooks/` is gitignored since lefthook generates machine-specific scripts.
- Update `AGENTS.md`: document pre-commit/pre-push hooks in Quality Gates, add `just fmt-all` and `just hooks` usage, upgrade worktree fmt gotcha from CI note to commit blocker, add `just mobile-fmt` to mobile commands; also backfill CLI-first updates (`SPROUT_AUTH_TAG`, complete exit codes, `--format compact` flag position, two new gotchas).

All hook commands delegate to `just` recipes as the single source of truth.
2026-05-26 22:05:42 +00:00
Wes
9c6b770aa5 feat(web): scaffold browser-based Sprout web client (#474)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-04 12:49:48 -07:00
Wes
a444fb9516 feat(mobile): scaffold Flutter app with Riverpod & Catppuccin theme (#306)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 09:55:14 -07:00
Wes
19d8b520bc Support old and new Claude ACP runtimes (#193) 2026-03-26 12:50:44 -07:00
Wes
629c99fd2f feat: add desktop app (#3) 2026-03-09 13:02:11 -07:00