From 1bfe0a05077148570eb0c647345f27c1751a49df Mon Sep 17 00:00:00 2001 From: Brent Rager Date: Thu, 6 Aug 2026 13:02:40 -0400 Subject: [PATCH 1/5] th-6578ee: attest-push-hint missed the shape agents actually use MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Shipped resolving the repo from the PreToolUse payload's `.cwd`. That is the SESSION's directory, and a session sits in one repo for its whole life — so agents write `cd /path/to/other-repo && git push`. The hook saw the session cwd, found no scripts/ci/attest.sh there, and exited 0. Silently. On the common case. Verified live 2026-08-06: a real `cd ~/dev/smooai/smooai && git push` from a smooth session pushed with no prompt, while the identical push with cwd=smooai correctly asked. My original 10-case suite passed only because I hand-fed it cwd values instead of the shape a real agent emits — the tests agreed with the bug. Now resolves last-wins: `git -C ` > last `cd ` > `.cwd`, with quote-stripping and ~ expansion. Also fixes push detection: `(-[^ ]+\s+)*push` let a flag's VALUE eat the `push` token, so `git -C /some/repo push` read as not-a-push. Flags that take an argument are now matched explicitly. 14/14 cases, including every miss above. --- .changeset/attest-hook-cwd.md | 9 +++++ .../smooth-agent/hooks/attest-push-hint.sh | 34 +++++++++++++++++-- 2 files changed, 41 insertions(+), 2 deletions(-) create mode 100644 .changeset/attest-hook-cwd.md diff --git a/.changeset/attest-hook-cwd.md b/.changeset/attest-hook-cwd.md new file mode 100644 index 000000000..143b19184 --- /dev/null +++ b/.changeset/attest-hook-cwd.md @@ -0,0 +1,9 @@ +--- +'@smooai/smooth': patch +--- + +smooth-agent: attest-push-hint missed `cd && git push` (the common shape) + +Resolved the repo from the session's `.cwd`, which is never where the command +runs — agents `cd` inline. Also fixed push-detection so `git -C push` is +recognised (the flag's value was eating the `push` token). diff --git a/claude-plugins/smooth-agent/hooks/attest-push-hint.sh b/claude-plugins/smooth-agent/hooks/attest-push-hint.sh index 761729929..06cdf485e 100755 --- a/claude-plugins/smooth-agent/hooks/attest-push-hint.sh +++ b/claude-plugins/smooth-agent/hooks/attest-push-hint.sh @@ -30,12 +30,42 @@ echo "$CMD" | grep -q 'attest:ack' && exit 0 # Only care about an actual `git push` that publishes commits. Skip the forms that # publish nothing a CI run would check: tag/branch deletion, --dry-run, and # `th pearls push` (a Dolt sync, not git). -echo "$CMD" | grep -qE '(^|[;&|]|\s)git\s+(-[^ ]+\s+)*push(\s|$)' || exit 0 +# The `(-C|--git-dir|--work-tree) ` alternative is load-bearing: those flags +# take a VALUE, so a naive `(-[^ ]+\s+)*push` lets the value eat the `push` token +# and `git -C /some/repo push` reads as "not a push". Caught in testing 2026-08-06. +echo "$CMD" | grep -qE '(^|[;&|]|\s)git\s+((-C|--git-dir|--work-tree)[[:space:]=]+[^ ]+\s+|-[^ ]+\s+)*push(\s|$)' || exit 0 echo "$CMD" | grep -qE '(--delete|--dry-run|-d\s|--tags)' && exit 0 +# WHERE the push will actually happen. `.cwd` is the SESSION's directory, which is +# not where the command runs: a session sits in one repo for its whole life, so +# agents write `cd /path/to/other-repo && git push`. Resolving from `.cwd` alone +# silently missed exactly that — the common case — and the hook shipped that way +# (verified 2026-08-06: real `cd …/smooai && git push` from a smooth session +# exited 0, while the same push with cwd=smooai correctly asked). +# +# Precedence, last wins, mirroring how the shell would actually end up: +# 1. `git -C push` — explicit, beats everything +# 2. the LAST `cd ` in the chain +# 3. `.cwd` +resolve_dir() { + local base target + base=$(echo "$INPUT" | jq -r '.cwd // "."' 2>/dev/null) + + target=$(echo "$CMD" | sed -nE 's/.*git[[:space:]]+-C[[:space:]]+([^[:space:];&|]+).*/\1/p' | tail -1) + [[ -z "$target" ]] && target=$(echo "$CMD" | sed -nE 's/.*(^|[;&|][[:space:]]*)cd[[:space:]]+([^[:space:];&|]+).*/\2/p' | tail -1) + + # Strip quotes an agent may have wrapped a path in, and expand a leading ~. + target=${target%\"}; target=${target#\"}; target=${target%\'}; target=${target#\'} + [[ "$target" == "~"* ]] && target="$HOME${target#\~}" + + [[ -z "$target" ]] && { echo "$base"; return; } + [[ "$target" == /* ]] && { echo "$target"; return; } + echo "$base/$target" +} + # Repo-agnostic by design: the hook fires only where the convention exists, so # copying scripts/ci/ into another repo turns this on there with no extra wiring. -ROOT=$(cd "$(echo "$INPUT" | jq -r '.cwd // "."' 2>/dev/null)" 2>/dev/null && git rev-parse --show-toplevel 2>/dev/null) +ROOT=$(cd "$(resolve_dir)" 2>/dev/null && git rev-parse --show-toplevel 2>/dev/null) [[ -n "$ROOT" && -x "$ROOT/scripts/ci/attest.sh" ]] || exit 0 # What could be credited here, straight from the directory — no list to keep in sync. From f3818f9507947493d3a2d4246df86ad47de29332 Mon Sep 17 00:00:00 2001 From: Brent Rager Date: Thu, 6 Aug 2026 13:06:37 -0400 Subject: [PATCH 2/5] th-6578ee: bump smooth-agent 0.4.0 -> 0.4.1 so the hook fix is installable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The fix branch and the installed copy both read 0.4.0, so `claude plugin update smooth-agent@smooth` could report 'already up to date' and never install it. Today's 0.2.0 -> 0.4.0 update only worked because a real version delta existed. A plugin change that ships no version bump is a change nobody can install. Bump both manifests together — `claude plugin tag` validates that plugin.json and the marketplace entry agree. --- .claude-plugin/marketplace.json | 2 +- claude-plugins/smooth-agent/.claude-plugin/plugin.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 03fc3e073..b70035747 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -13,7 +13,7 @@ "name": "smooth-agent", "source": "./claude-plugins/smooth-agent", "description": "Run a Big Smooth that drives Claude Code worker sessions over tmux, coordinate agents over th-mail, drive the org's Smooth Operator, and track work in pearls. Provides the /smooth command + a windows-build-box skill (throwaway Windows EC2 over SSM). Ships the shared SmooAI repo guardrail hooks (worktree enforcement, th-over-curl nudges, pearls-label reminder, pearl-store read-only-wedge guard).", - "version": "0.4.0", + "version": "0.4.1", "category": "orchestration", "keywords": ["orchestration", "tmux", "multi-agent", "th-mail", "pearls", "smooth-operator"], "author": { diff --git a/claude-plugins/smooth-agent/.claude-plugin/plugin.json b/claude-plugins/smooth-agent/.claude-plugin/plugin.json index ada40e48b..78487eafc 100644 --- a/claude-plugins/smooth-agent/.claude-plugin/plugin.json +++ b/claude-plugins/smooth-agent/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "$schema": "https://anthropic.com/claude-code/plugin.schema.json", "name": "smooth-agent", - "version": "0.4.0", + "version": "0.4.1", "description": "Big Smooth orchestration for Claude Code: tmux-supervised worker sessions that run in parallel, coordinate over th-mail, and track work in pearls. Auto-onboards every Claude Code session to the th-mail bus at startup, then nudges it to self-rename to a task-meaningful handle. Provides the /smooth command and a windows-build-box skill (throwaway Windows EC2 over SSM). Also ships the shared SmooAI repo guardrails as hooks (worktree enforcement, th-over-curl nudges, pearls-label reminder, pearl-store read-only-wedge guard) so the SmooAI repos stop hand-copying .claude/hooks.", "author": { "name": "SmooAI", From ef70185ba963cac53e27ded264ea26f0c512b30a Mon Sep 17 00:00:00 2001 From: Brent Rager Date: Thu, 6 Aug 2026 13:09:05 -0400 Subject: [PATCH 3/5] th-6578ee: plugin version rides the changeset release, like everything else MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The plugin manifests carried their OWN hand-maintained version, so they drifted from the repo. smooth-agent sat installed at 0.2.0 for 26 days (2026-07-11 -> 2026-08-06) while this repo shipped 20+ versions. Two hooks merged in that window never ran on anyone's machine, and attest-push-hint was reported as 'live' three separate times while doing nothing. `claude plugin update` only reinstalls on a version DELTA, so a plugin change shipped without a bump is a change nobody can install. Relying on remembering to bump by hand is what produced the 26-day gap. sync-versions.mjs already propagates package.json -> Cargo.toml + Cargo.lock during `pnpm ci:version` (changesets/action -> ci-version.mjs). Adding the two plugin manifests to that same list means every release bumps the plugin, so an installed copy is always comparable to the published one. Over-bumping when only Rust changed is harmless — plugin update just reinstalls the same hooks. plugin.json and the marketplace entry are bumped together because `claude plugin tag` validates that they agree. Verified: sync-versions.mjs takes both to 0.26.5 and both stay valid JSON. --- .claude-plugin/marketplace.json | 2 +- .../smooth-agent/.claude-plugin/plugin.json | 2 +- scripts/sync-versions.mjs | 31 +++++++++++++++++++ 3 files changed, 33 insertions(+), 2 deletions(-) diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index b70035747..299a355bd 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -13,7 +13,7 @@ "name": "smooth-agent", "source": "./claude-plugins/smooth-agent", "description": "Run a Big Smooth that drives Claude Code worker sessions over tmux, coordinate agents over th-mail, drive the org's Smooth Operator, and track work in pearls. Provides the /smooth command + a windows-build-box skill (throwaway Windows EC2 over SSM). Ships the shared SmooAI repo guardrail hooks (worktree enforcement, th-over-curl nudges, pearls-label reminder, pearl-store read-only-wedge guard).", - "version": "0.4.1", + "version": "0.26.5", "category": "orchestration", "keywords": ["orchestration", "tmux", "multi-agent", "th-mail", "pearls", "smooth-operator"], "author": { diff --git a/claude-plugins/smooth-agent/.claude-plugin/plugin.json b/claude-plugins/smooth-agent/.claude-plugin/plugin.json index 78487eafc..93ecfe321 100644 --- a/claude-plugins/smooth-agent/.claude-plugin/plugin.json +++ b/claude-plugins/smooth-agent/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "$schema": "https://anthropic.com/claude-code/plugin.schema.json", "name": "smooth-agent", - "version": "0.4.1", + "version": "0.26.5", "description": "Big Smooth orchestration for Claude Code: tmux-supervised worker sessions that run in parallel, coordinate over th-mail, and track work in pearls. Auto-onboards every Claude Code session to the th-mail bus at startup, then nudges it to self-rename to a task-meaningful handle. Provides the /smooth command and a windows-build-box skill (throwaway Windows EC2 over SSM). Also ships the shared SmooAI repo guardrails as hooks (worktree enforcement, th-over-curl nudges, pearls-label reminder, pearl-store read-only-wedge guard) so the SmooAI repos stop hand-copying .claude/hooks.", "author": { "name": "SmooAI", diff --git a/scripts/sync-versions.mjs b/scripts/sync-versions.mjs index 4ee610f34..2f7fa8765 100644 --- a/scripts/sync-versions.mjs +++ b/scripts/sync-versions.mjs @@ -28,7 +28,38 @@ if (!version) { process.exit(1); } +// The Claude Code plugin ships this repo's hooks (worktree enforcement, +// th-over-curl, attest-push-hint). Its manifests carry their OWN version, and +// `claude plugin update` only reinstalls when it sees a version DELTA — so a +// plugin change that ships without a bump is a change nobody can install. +// +// That is not hypothetical: smooth-agent sat installed at 0.2.0 for 26 days +// (2026-07-11 → 2026-08-06) while this repo shipped 20+ versions. Two hooks +// merged in that window simply never ran on anyone's machine, and the one that +// prompted this — attest-push-hint — was reported as "live" three separate +// times while doing nothing. +// +// Riding the changeset version removes the judgment call entirely: every +// release bumps the plugin, so an installed copy is always comparable to the +// published one. Over-bumping (plugin version moves when only Rust changed) is +// harmless — `claude plugin update` just reinstalls the same hooks. +// +// plugin.json and the marketplace entry MUST agree; `claude plugin tag` +// validates that, so both are updated here together. +const pluginVersion = (label) => ({ + path: label, + apply(content) { + const pattern = /("version"\s*:\s*")([^"]+)(")/; + if (!pattern.test(content)) { + throw new Error(`"version" not found in ${label}`); + } + return content.replace(pattern, `$1${version}$3`); + }, +}); + const updates = [ + pluginVersion("claude-plugins/smooth-agent/.claude-plugin/plugin.json"), + pluginVersion(".claude-plugin/marketplace.json"), { path: "Cargo.toml", apply(content) { From 071fc3ace1a1614d9f556adf58ea867edb86690b Mon Sep 17 00:00:00 2001 From: Brent Rager Date: Thu, 6 Aug 2026 13:10:43 -0400 Subject: [PATCH 4/5] th-16ff70: sync-versions skipped operator git-deps by an exact NAME LIST MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Cargo.lock exception matched `smooai-smooth-operator(-core|-server)?` — an allowlist of three names. Two more crates joined that dep graph upstream: smooai-smooth-operator-adapter-memory smooai-smooth-operator-ingestion Neither is in the list, so every `pnpm version:sync` rewrote their locked version from the smooth-operator release (1.23.1) to this workspace's (0.26.5). That is the churn in th-16ff70 — 'flips 0.23.x<->1.23.x on every local build, DIRTY-PR tax on all lock-touching PRs' — and it also breaks `cargo --locked`, since that source never published 0.26.5. Matched by PREFIX now. A hand-maintained allowlist cannot track a dependency graph that grows in someone else's repo; the invariant is 'external operator crate', and the prefix expresses it. Safe because NO crate in this workspace is named smooai-smooth-operator* — the engine is a separate repo (CLAUDE.md §1). Verified: sync-versions is now idempotent on a synced tree ('No files were updated') and leaves Cargo.lock untouched. --- scripts/sync-versions.mjs | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/scripts/sync-versions.mjs b/scripts/sync-versions.mjs index 2f7fa8765..5fcbb8948 100644 --- a/scripts/sync-versions.mjs +++ b/scripts/sync-versions.mjs @@ -149,19 +149,26 @@ const updates = [ // smooth-operator repo — their locked versions track that repo, NOT // the workspace version. Bumping a lock entry to a workspace version // that source never published breaks `cargo` resolution under - // `--locked` ("= "*" locked to 0.23.0 … candidate 1.23.1"). Three - // package names, matched exactly: - // - `smooai-smooth-operator-core` (crates.io engine) - // - `smooai-smooth-operator-server` (git dep) - // - `smooai-smooth-operator` (git dep, the svc crate) - // The Cargo.toml dep skip (above) keys on `git =`, but lock entries - // carry `source` on a separate line outside this 2-line match, so - // here we skip by exact name. Pearl th-1ee32b (the lock twin the - // git-dep Cargo.toml fix in #260 left behind). + // `--locked` ("= "*" locked to 0.23.0 … candidate 1.23.1"). Pearl + // th-1ee32b (the lock twin the git-dep Cargo.toml fix in #260 left + // behind). + // + // Matched by PREFIX, not by an exact list. It used to be + // `(-core|-server)?`, which silently missed + // `smooai-smooth-operator-adapter-memory` and + // `smooai-smooth-operator-ingestion` once those joined the dep graph + // — so every `version:sync` rewrote two git-dep entries and every + // lock-touching PR carried the churn (pearl th-16ff70, "flips + // 0.23.x↔1.23.x on every local build"). + // + // A hand-maintained allowlist cannot track a dependency graph that + // grows upstream. The prefix is safe because NO crate in this + // workspace is named `smooai-smooth-operator*` — the engine lives in + // its own repo (see CLAUDE.md §1); `ls crates/` is the check. const pattern = /(name = "smooai-smooth-[^"]+"\nversion = ")([^"]+)(")/g; return content.replace(pattern, (match, pre, _ver, post) => { - if (/name = "smooai-smooth-operator(-core|-server)?"\n/.test(pre)) { + if (/name = "smooai-smooth-operator[^"]*"\n/.test(pre)) { return match; } return `${pre}${version}${post}`; From 99d0a39bb19f1b371c9a33c43352461753528734 Mon Sep 17 00:00:00 2001 From: Brent Rager Date: Thu, 6 Aug 2026 13:15:17 -0400 Subject: [PATCH 5/5] =?UTF-8?q?th-6578ee:=20path-filter=20PR=20checks=20?= =?UTF-8?q?=E2=80=94=20stop=20compiling=20Rust=20twice=20for=20a=20shell?= =?UTF-8?q?=20script?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR is the case in point: four commits touching one bash hook, two JSON manifests, and a JS script triggered 'Rust checks (ubuntu-latest)', 'Rust checks (windows-latest)' and 'Web checks' — a full workspace compile on two platforms, Windows billing at 2x, plus a Vite build. ~45 min of runner time to validate a shell script. Gates STEPS, not jobs. A workflow-level `paths:` would mean the checks never appear at all, and branch protection requires them — the PR would block forever. This is the same reason the `push:` trigger exists (pearl th-e392d9) and the same pattern smooai's pr-checks uses. PR events only. push-to-main and merge_group have no reliable base to diff, and those are the moments a wrongly-skipped check is most expensive, so they run everything. rust triggers on crates/**, Cargo.{toml,lock}, rust-toolchain.toml, rustfmt.toml, go/**, build-smooth-dolt.sh, and this workflow. web on crates/smooth-web/web/** and this workflow. Both include the workflow itself so a change to the gating always exercises what it gates. Verified: YAML parses, permissions carry pull-requests: read (dorny needs it on pull_request), 16/19 rust steps and 4/7 web steps gated — the ungated three per job are checkout, filter, and gate. --- .changeset/selective-pr-ci.md | 10 ++++ .github/workflows/pr-checks.yml | 86 ++++++++++++++++++++++++++++++--- 2 files changed, 88 insertions(+), 8 deletions(-) create mode 100644 .changeset/selective-pr-ci.md diff --git a/.changeset/selective-pr-ci.md b/.changeset/selective-pr-ci.md new file mode 100644 index 000000000..2d375c28d --- /dev/null +++ b/.changeset/selective-pr-ci.md @@ -0,0 +1,10 @@ +--- +'@smooai/smooth': patch +--- + +CI: path-filter PR checks so a non-Rust change stops compiling the workspace twice + +`Rust checks` (ubuntu + windows) and `Web checks` ran unconditionally, so a +bash/JSON/doc-only PR burned ~45 min of runner time — Windows bills at 2x — to +validate a shell script. Steps are now gated on `dorny/paths-filter`; the jobs +still run and report so branch protection is unaffected. diff --git a/.github/workflows/pr-checks.yml b/.github/workflows/pr-checks.yml index feb415016..e48515763 100644 --- a/.github/workflows/pr-checks.yml +++ b/.github/workflows/pr-checks.yml @@ -15,6 +15,11 @@ on: env: FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: 'true' +# dorny/paths-filter queries a PR's changed files through the API. +permissions: + contents: read + pull-requests: read + jobs: rust: # Pearl th-a165b4: Windows joins the test matrix so cross-platform @@ -60,31 +65,73 @@ jobs: steps: - uses: actions/checkout@v4 + # th-6578ee: a bash/JSON/doc-only PR was compiling the whole Rust + # workspace on TWO platforms (Windows bills at 2x) plus a full web + # build — ~45 min of runner time to validate a shell script. + # + # The filter gates STEPS, not the job. The job must still run and + # report: branch protection requires these checks, and a workflow- + # level `paths:` would make them never appear at all, blocking the PR + # forever. Same reason the `push:` trigger exists (pearl th-e392d9). + # + # PR events only. On push-to-main and merge_group there is no + # reliable base to diff against, and those are exactly the moments a + # skipped check would be expensive to be wrong about — so they run + # everything. + - name: Detect changes + id: filter + if: github.event_name == 'pull_request' + uses: dorny/paths-filter@v3 + with: + filters: | + rust: + - 'crates/**' + - 'Cargo.toml' + - 'Cargo.lock' + - 'rust-toolchain.toml' + - 'rustfmt.toml' + - 'go/**' + - 'scripts/build-smooth-dolt.sh' + - '.github/workflows/pr-checks.yml' + + # One boolean instead of repeating the compound condition on a dozen + # steps. `shell: bash` because the Windows leg defaults to pwsh, + # where `>> $GITHUB_OUTPUT` is `$env:GITHUB_OUTPUT`. + - name: Gate + id: gate + shell: bash + run: echo "run=${{ github.event_name != 'pull_request' || steps.filter.outputs.rust == 'true' }}" >>"$GITHUB_OUTPUT" + - name: Install system dependencies - if: runner.os == 'Linux' + if: steps.gate.outputs.run == 'true' && runner.os == 'Linux' run: sudo apt-get update && sudo apt-get install -y libdbus-1-dev libcap-ng-dev libicu-dev pkg-config protobuf-compiler # No apt on Windows — protoc is the only native build-time tool the # workspace needs there (tonic-build in smooth-scribe et al.). - name: Install protoc - if: runner.os == 'Windows' + if: steps.gate.outputs.run == 'true' && runner.os == 'Windows' uses: arduino/setup-protoc@v3 with: repo-token: ${{ secrets.GITHUB_TOKEN }} - uses: dtolnay/rust-toolchain@stable + if: steps.gate.outputs.run == 'true' with: components: rustfmt, clippy - uses: Swatinem/rust-cache@v2 + if: steps.gate.outputs.run == 'true' - uses: pnpm/action-setup@v4 + if: steps.gate.outputs.run == 'true' - uses: actions/setup-node@v4 + if: steps.gate.outputs.run == 'true' with: node-version: 22 - name: Build web UI + if: steps.gate.outputs.run == 'true' shell: bash run: | cd crates/smooth-web/web @@ -101,23 +148,24 @@ jobs: # TCP transport is pearl th-5f35a5) — but the binary must keep # compiling so it can't silently regress with another unix-only call. - name: Build smooth-dolt (embedded Dolt for pearl memory tests) - if: runner.os == 'Linux' + if: steps.gate.outputs.run == 'true' && runner.os == 'Linux' run: bash scripts/build-smooth-dolt.sh - name: Build smooth-dolt (Windows — CGO via mingw, gms_pure_go drops ICU) - if: runner.os == 'Windows' + if: steps.gate.outputs.run == 'true' && runner.os == 'Windows' shell: bash run: cd go/smooth-dolt && CGO_ENABLED=1 go build -tags gms_pure_go -o smooth-dolt.exe . - name: Install nextest + if: steps.gate.outputs.run == 'true' uses: taiki-e/install-action@nextest - name: Format check - if: runner.os == 'Linux' + if: steps.gate.outputs.run == 'true' && runner.os == 'Linux' run: cargo fmt --check - name: Clippy - if: runner.os == 'Linux' + if: steps.gate.outputs.run == 'true' && runner.os == 'Linux' # Honor the lint levels declared in [workspace.lints]: clippy::all # (deny) + unused_must_use/unsafe_code fail the build; pedantic and # nursery are advisory `warn` by design, so a blanket `-D warnings` @@ -125,11 +173,13 @@ jobs: run: cargo clippy - name: Build examples (needed by cross-process file-lock test, pearl th-9799fa) + if: steps.gate.outputs.run == 'true' run: cargo build --examples --workspace # bash on both legs so the filter expression quotes predictably # (pwsh would need its own escaping for the `&` and parentheses). - name: Test + if: steps.gate.outputs.run == 'true' shell: bash run: cargo nextest run --profile ci ${{ matrix.test_args }} env: @@ -138,7 +188,7 @@ jobs: # One report per PR — publishing from both legs would double every # entry in the check summary. - name: Publish test report - if: always() && runner.os == 'Linux' + if: always() && steps.gate.outputs.run == 'true' && runner.os == 'Linux' uses: ctrf-io/github-test-reporter@v1 with: report-path: target/nextest/ci/junit.xml @@ -146,7 +196,7 @@ jobs: continue-on-error: true - name: Build - if: runner.os == 'Linux' + if: steps.gate.outputs.run == 'true' && runner.os == 'Linux' run: cargo build --release -p smooai-smooth-cli web: @@ -156,14 +206,34 @@ jobs: steps: - uses: actions/checkout@v4 + # Same step-gating rationale as the rust job above: the job must keep + # reporting for branch protection, so only the work is skipped. + - name: Detect changes + id: filter + if: github.event_name == 'pull_request' + uses: dorny/paths-filter@v3 + with: + filters: | + web: + - 'crates/smooth-web/web/**' + - '.github/workflows/pr-checks.yml' + + - name: Gate + id: gate + run: echo "run=${{ github.event_name != 'pull_request' || steps.filter.outputs.web == 'true' }}" >>"$GITHUB_OUTPUT" + - uses: pnpm/action-setup@v4 + if: steps.gate.outputs.run == 'true' - uses: actions/setup-node@v4 + if: steps.gate.outputs.run == 'true' with: node-version: 22 - name: Install + if: steps.gate.outputs.run == 'true' run: cd crates/smooth-web/web && pnpm install --no-frozen-lockfile - name: Build + if: steps.gate.outputs.run == 'true' run: cd crates/smooth-web/web && pnpm build