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
name: CI
on:
push:
branches:
pull_request:
branches:
# Cancel superseded runs on the same branch. The matrix test job takes
# 40-90 s on each runner and there's no value in finishing a stale run
# after a force-push.
concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true
env:
CARGO_TERM_COLOR: always
RUST_BACKTRACE: short
jobs:
# ── Core: fmt + clippy + test on Ubuntu and macOS ────────────────────────
#
# Same command set the pre-commit hook runs locally, so a green hook
# means a green job here (modulo platform-specific test failures).
test:
strategy:
fail-fast: false
matrix:
os:
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
components: clippy, rustfmt
- uses: Swatinem/rust-cache@v2
- name: Install ffmpeg
if: runner.os == 'Linux'
run: sudo apt-get update && sudo apt-get install -y ffmpeg
- name: Install ffmpeg (macOS)
if: runner.os == 'macOS'
run: brew install ffmpeg
- run: cargo fmt --check
- run: cargo clippy -- -D warnings
- run: cargo test
# ── MSRV check: confirm the declared minimum still builds + tests ────────
#
# `rust-version` in Cargo.toml is the source of truth; this job re-runs
# the binary build + unit tests on exactly that toolchain to catch any
# silently-newer-Rust-feature creep.
msrv:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Read rust-version from Cargo.toml
id: msrv
run: |
msrv=$(grep -oE '^rust-version = "[^"]+"' Cargo.toml | sed -E 's/.*"([^"]+)"/\1/')
if [ -z "$msrv" ]; then
echo "rust-version not declared in Cargo.toml"; exit 1
fi
echo "msrv=$msrv" >> "$GITHUB_OUTPUT"
# dtolnay/rust-toolchain has no @stable-equivalent ref for arbitrary
# toolchain inputs (the @stable / @nightly / @beta refs only work
# for those exact channels) — @master is the documented entry point
# for passing a `toolchain:` input. The action is small and audit-
# able, and Dependabot watches it for upstream changes.
- uses: dtolnay/rust-toolchain@master
with:
toolchain: ${{ steps.msrv.outputs.msrv }}
- uses: Swatinem/rust-cache@v2
with:
key: msrv-${{ steps.msrv.outputs.msrv }}
- run: sudo apt-get update && sudo apt-get install -y ffmpeg
- run: cargo build
# Run the full suite (unit + integration). MSRV regressions in a
# test target should fail here too.
- run: cargo test --all-targets
# ── Doc build: catches broken intra-doc links + missing `///` on pub ─────
doc:
runs-on: ubuntu-latest
env:
# Treat warnings (private item without docs, broken links) as errors
# so doc rot is caught at PR time, not at docs.rs build time.
RUSTDOCFLAGS: "-D warnings"
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- run: cargo doc --no-deps --document-private-items
# ── Security audit ────────────────────────────────────────────────────────
#
# rustsec/audit-check runs against the RustSec advisory DB. Fails the
# PR if any dep has a published CVE. Dependabot opens the upgrade PR;
# this job ensures the advisory is surfaced even if dependabot is slow.
audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: rustsec/audit-check@v2.0.0
with:
token: ${{ secrets.GITHUB_TOKEN }}
# ── License + supply-chain policy via cargo-deny ─────────────────────────
#
# See deny.toml for the policy. Notably we reject GPL / AGPL deps (this
# crate is MIT) and require every dep to ship at least one accepted
# license. Bans on duplicate crates catch accidental MSRV-driven
# double-pinning.
deny:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: EmbarkStudios/cargo-deny-action@v2
with:
command: check
arguments: --all-features
# ── Spell-check prose in source files + docs ─────────────────────────────
typos:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: crate-ci/typos@v1.46.1
# ── Coverage upload (codecov.io) ─────────────────────────────────────────
#
# cargo-llvm-cov runs the test suite under instrumentation, emits an
# lcov.info, and codecov-action posts to codecov.io. Best-effort
# against an external service: failure to upload doesn't fail the PR.
coverage:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
components: llvm-tools-preview
- uses: Swatinem/rust-cache@v2
with:
key: coverage
- run: sudo apt-get update && sudo apt-get install -y ffmpeg
- name: Install cargo-llvm-cov
uses: taiki-e/install-action@v2
with:
tool: cargo-llvm-cov
- name: Collect coverage
# `--all-targets` so the lcov reflects both unit and integration
# tests; otherwise tests/cli_smoke.rs coverage is invisible.
run: cargo llvm-cov --all-targets --lcov --output-path lcov.info
- name: Upload to codecov.io
uses: codecov/codecov-action@v6
with:
files: lcov.info
fail_ci_if_error: false
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
# ── apt repo script tests (BATS) ─────────────────────────────────────────
#
# Runs tests/apt_repo.bats, which exercises update-apt-repo.sh against
# real minimal .deb fixtures. Ubuntu-only: needs apt-ftparchive.
apt-repo-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install dependencies
run: sudo apt-get install -y --no-install-recommends apt-utils bats binutils
- name: Run apt repo tests
run: bats tests/apt_repo.bats
# ── Publish prompt log to jackdanger.com ─────────────────────────────────
#
# Picks up new / modified files under `promptlog/*.md` on push to main
# and POSTs each as text/markdown to https://jackdanger.com/promptlog/.
# Authenticated via the PROMPTLOG_TOKEN repository secret; if absent
# (forks, dependabot branches) the job logs what it would have done
# and exits zero.
publish-promptlog:
runs-on: ubuntu-latest
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v4
with:
# `0` fetches the full history. We diff `before..sha` to catch
# promptlog touches anywhere in the pushed range (batch merges,
# manual pushes of several commits) rather than just the tip.
fetch-depth: 0
- name: Find changed promptlog files
id: changed
env:
BEFORE_SHA: ${{ github.event.before }}
AFTER_SHA: ${{ github.sha }}
run: |
# For the very first push to a branch BEFORE_SHA is all zeros
# (no prior commit on this ref). Fall back to HEAD~1 there;
# the workflow only runs on main so the fallback is safe.
if [ "$BEFORE_SHA" = "0000000000000000000000000000000000000000" ] || [ -z "$BEFORE_SHA" ]; then
BEFORE_SHA="${AFTER_SHA}~1"
fi
changed=$(git diff --name-only --diff-filter=AM "$BEFORE_SHA".."$AFTER_SHA" \
| grep -E '^promptlog/.+\.md$' \
| grep -v '^promptlog/README\.md$' || true)
if [ -z "$changed" ]; then
echo "no promptlog files changed; nothing to publish"
echo "files=" >> "$GITHUB_OUTPUT"
exit 0
fi
echo "files<<EOF" >> "$GITHUB_OUTPUT"
echo "$changed" >> "$GITHUB_OUTPUT"
echo "EOF" >> "$GITHUB_OUTPUT"
- name: Publish each file
if: steps.changed.outputs.files != ''
env:
PROMPTLOG_TOKEN: ${{ secrets.PROMPTLOG_TOKEN }}
run: |
set -eu
if [ -z "${PROMPTLOG_TOKEN:-}" ]; then
echo "PROMPTLOG_TOKEN not configured — skipping upload."
echo "Would have published:"
echo "${{ steps.changed.outputs.files }}"
exit 0
fi
while IFS= read -r file; do
[ -z "$file" ] && continue
slug=$(basename "$file" .md)
echo "→ POST $file → https://jackdanger.com/promptlog/$slug"
# --retry handles transient network blips so a 5xx burst
# doesn't fail an otherwise-green main build. --retry-all-errors
# so curl also retries connection-level failures, not just
# HTTP status codes. --max-time guards against an endpoint
# that hangs holding the connection.
curl --fail-with-body -sS \
--retry 5 --retry-all-errors --retry-delay 5 \
--max-time 60 \
-X POST "https://jackdanger.com/promptlog/$slug" \
-H "Authorization: Bearer $PROMPTLOG_TOKEN" \
-H "Content-Type: text/markdown" \
--data-binary "@$file"
echo
done <<< "${{ steps.changed.outputs.files }}"