1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
name: CI
on:
push:
branches:
tags:
pull_request:
branches:
env:
CARGO_TERM_COLOR: always
jobs:
test:
name: Test
runs-on: ubuntu-latest
strategy:
matrix:
rust:
- stable
- beta
- nightly
features:
- ""
- "mcp"
steps:
- uses: actions/checkout@v6
- uses: dtolnay/rust-toolchain@master
with:
toolchain: ${{ matrix.rust }}
components: rustfmt
- uses: Swatinem/rust-cache@v2
- name: Install ALSA headers for cpal
run: sudo apt-get update && sudo apt-get install -y libasound2-dev
- name: Run tests
run: |
if [ -z "${{ matrix.features }}" ]; then
cargo test --verbose
else
cargo test --features "${{ matrix.features }}" --verbose
fi
mcp-build:
name: MCP Release Build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- name: Install ALSA headers for cpal
run: sudo apt-get update && sudo apt-get install -y libasound2-dev
- name: Build omni-dev-mcp (release)
run: cargo build --release --features mcp --bin omni-dev-mcp
fmt:
name: Rustfmt
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt
- name: Check formatting
run: cargo fmt --all -- --check
clippy:
name: Clippy
runs-on: ubuntu-latest
strategy:
matrix:
features:
- ""
- "mcp"
steps:
- uses: actions/checkout@v6
- uses: dtolnay/rust-toolchain@stable
with:
components: clippy
- uses: Swatinem/rust-cache@v2
- name: Install ALSA headers for cpal
run: sudo apt-get update && sudo apt-get install -y libasound2-dev
- name: Run clippy
run: |
if [ -z "${{ matrix.features }}" ]; then
cargo clippy --all-targets -- -D warnings
else
cargo clippy --all-targets --features "${{ matrix.features }}" -- -D warnings
fi
docs:
name: Docs
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- name: Install ALSA headers for cpal
run: sudo apt-get update && sudo apt-get install -y libasound2-dev
- name: Build docs
run: cargo doc --no-deps --document-private-items
coverage:
name: Coverage
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write # required to post the coverage comment
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0 # full history so `git merge-base` can resolve the PR's fork point
- uses: dtolnay/rust-toolchain@stable
with:
components: llvm-tools-preview
- uses: Swatinem/rust-cache@v2
- name: Install ALSA headers for cpal
run: sudo apt-get update && sudo apt-get install -y libasound2-dev
- name: Install cargo-llvm-cov
uses: taiki-e/install-action@cargo-llvm-cov
# Every cargo step below sources cargo-llvm-cov's instrumentation env (rustc
# wrapper, flags, profile path) so they all share ONE set of dependency
# artifacts: the tests build them, and the omni-dev binary build reuses them
# (a plain `cargo build` differs in RUSTC_WRAPPER and would recompile the
# whole tree). `show-env` is deterministic, so sourcing it per step
# re-establishes the same env without leaking it into the worktree fallback
# below (which must run its own, separate coverage at the merge-base).
# This is the documented manual equivalent of the all-in-one `cargo llvm-cov`.
- name: Run tests with coverage
run: |
# shellcheck disable=SC1090
source <(cargo llvm-cov show-env --sh)
cargo llvm-cov clean --workspace
cargo test --all-features --workspace
- name: Generate codecov.json
# Kept ready so the disabled upload below works as soon as it is re-enabled.
run: |
# shellcheck disable=SC1090
source <(cargo llvm-cov show-env --sh)
cargo llvm-cov report --codecov --output-path codecov.json
- name: Generate head coverage report (lcov)
# Per-line lcov for the head commit. This is the input to
# `omni-dev coverage diff`: it drives patch coverage (of the lines this PR
# added, how many are covered — needs only the head report + diff) and,
# paired with the merge-base baseline below, the per-file deltas and
# indirect-change detection. Published as the baseline on `main` for PRs.
run: |
# shellcheck disable=SC1090
source <(cargo llvm-cov show-env --sh)
cargo llvm-cov report --lcov --output-path coverage-head.lcov
- name: Build coverage summary
# Full per-file summary: appended to the run's Summary tab and published as
# a build artifact (see "Upload coverage artifacts"). It is intentionally
# NOT inlined into the PR comment, which shows changed files and patch lines.
run: |
# shellcheck disable=SC1090
source <(cargo llvm-cov show-env --sh)
cargo llvm-cov report --summary-only | tee coverage-summary.txt
{
echo '## Coverage'
echo '```'
cat coverage-summary.txt
echo '```'
} >> "$GITHUB_STEP_SUMMARY"
# No separate binary build: `cargo test` above already compiled the
# `omni-dev` executable (target/debug/omni-dev, part of the test build graph)
# with the same instrumentation env. The PR-comment step below runs it
# directly. Building it separately with `cargo build --bin` would recompile a
# dozen crates whose features differ once dev-dependencies drop out.
- name: Upload coverage artifacts
# The full summary lives here (not in the PR comment); codecov.json kept for
# when the upload below is re-enabled. Runs before the comment so the comment
# can link to this artifact via the step's `artifact-url` output.
id: upload
uses: actions/upload-artifact@v7
with:
name: coverage-summary
path: |
coverage-summary.txt
codecov.json
coverage-head.lcov
- name: Determine merge-base
# The PR's fork point on main. Pinning the baseline to this immutable commit
# (rather than "latest main") makes per-file deltas attributable to THIS PR
# alone — other PRs merging into main while this one is open can't leak into
# the numbers, and there is no race with in-flight main runs.
id: mb
if: github.event_name == 'pull_request'
run: echo "sha=$(git merge-base origin/main HEAD)" >> "$GITHUB_OUTPUT"
- name: Download baseline coverage for merge-base
# Pulls the per-line lcov published by the main run for the exact merge-base
# commit. A miss (fork point predates the feature / artifact retention) is a
# warning, not a failure — the fallback below recomputes it.
id: baseline
if: github.event_name == 'pull_request'
uses: dawidd6/action-download-artifact@v21
with:
name: coverage-baseline
commit: ${{ steps.mb.outputs.sha }}
workflow: ci.yml
path: baseline
if_no_artifact_found: warn
- name: Compute baseline from merge-base (fallback)
# Runs whenever the merge-base has no usable lcov baseline: a cold start,
# an expired artifact, or a pre-migration aggregate-only artifact (which
# the download above fetches but which lacks the lcov). Builds coverage at
# that exact commit in a worktree, so the comparison stays correct (and
# still PR-attributable). A no-op when the lcov baseline was downloaded.
if: github.event_name == 'pull_request'
run: |
if [ -f baseline/coverage-head.lcov ]; then
echo "lcov baseline already present for the merge-base; skipping recompute"
exit 0
fi
git worktree add ../base "${{ steps.mb.outputs.sha }}"
( cd ../base && cargo llvm-cov --all-features --workspace --no-report \
&& cargo llvm-cov report --lcov --output-path coverage-head.lcov )
mkdir -p baseline
# Rewrite the worktree's absolute SF paths to the main workspace prefix so
# `omni-dev coverage diff` strips one prefix for both head and baseline.
sed "s#$(cd ../base && pwd)/#$GITHUB_WORKSPACE/#g" \
../base/coverage-head.lcov > baseline/coverage-head.lcov
- name: Build PR coverage comment
# Single comprehensive comment: total + delta vs main, the per-file
# before/after table, PATCH COVERAGE (what this PR's added lines cover, with
# the exact uncovered file:line list), and indirect coverage flips. The
# baseline is optional — without it, patch coverage and the uncovered-line
# list still render (deltas/indirect are then omitted with an explanation).
if: github.event_name == 'pull_request'
env:
ARTIFACT_URL: ${{ steps.upload.outputs.artifact-url }}
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
BASE_SHA: ${{ steps.mb.outputs.sha }}
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
COMMIT_URL: ${{ github.server_url }}/${{ github.repository }}/commit
run: |
args=(coverage diff
--report coverage-head.lcov
--base-ref "$BASE_SHA"
--format markdown
--collapse-ranges
--artifact-url "$ARTIFACT_URL"
--run-url "$RUN_URL"
--base-sha "$BASE_SHA"
--head-sha "$HEAD_SHA"
--commit-url "$COMMIT_URL")
# Diff is computed merge-base..HEAD (the checked-out commit the coverage
# was measured on), attributing patch coverage to exactly this PR.
if [ -f baseline/coverage-head.lcov ]; then
args+=(--baseline-report baseline/coverage-head.lcov)
fi
./target/debug/omni-dev "${args[@]}" > coverage.md
- name: Comment coverage on PR
if: github.event_name == 'pull_request'
uses: marocchino/sticky-pull-request-comment@v3
with:
header: coverage # sticky key: updates the same comment each run
path: coverage.md
- name: Publish coverage baseline
# On main only: this run's per-line lcov becomes the baseline that
# subsequent PRs download above to compute deltas and indirect changes.
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
uses: actions/upload-artifact@v7
with:
name: coverage-baseline
path: coverage-head.lcov
# Publishing to codecov.io is temporarily DISABLED: an upstream outage in the
# codecov-action GPG uploader-key import randomly fails signature verification
# (codecov/codecov-action#1876). The printed summary + PR comment above stand
# in for it. Re-enable by changing `if: false` back to a real condition once
# Codecov's uploader key server recovers.
- name: Upload to codecov.io
if: false
uses: codecov/codecov-action@v7
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: codecov.json
fail_ci_if_error: true
- name: Enforce coverage threshold
# Runs last so the summary and PR comment are posted even when the gate fails.
run: |
# shellcheck disable=SC1090
source <(cargo llvm-cov show-env --sh)
cargo llvm-cov report --summary-only --fail-under-lines 30
audit:
name: Security Audit
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Install cargo-audit
run: cargo install cargo-audit --locked
- name: Run cargo-audit
run: cargo audit
deny:
name: Dependency Policy
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: EmbarkStudios/cargo-deny-action@v2.0.20
with:
command: check all
secrets:
name: Secret Scanning
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
- name: TruffleHog scan
uses: trufflesecurity/trufflehog@main
with:
extra_args: --only-verified