fix(audio): mic-first HUD, stop-path drain, silence pad, hangover tuning #224
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: CI | |
| on: | |
| pull_request: | |
| branches: [main] | |
| push: | |
| branches: [main] | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: true | |
| # --------------------------------------------------------------------------- | |
| # Job: build-and-test | |
| # Blocks PR merge. Must pass before any code lands on main. | |
| # --------------------------------------------------------------------------- | |
| jobs: | |
| build-and-test: | |
| name: Build & Test (macOS) | |
| runs-on: [self-hosted, macOS] | |
| steps: | |
| - name: Runner info | |
| run: | | |
| echo "Hostname: $(hostname)" | |
| echo "OS: $(sw_vers -productName) $(sw_vers -productVersion)" | |
| echo "Xcode path: $(xcode-select -p 2>/dev/null || echo 'not set')" | |
| echo "Xcode: $(xcodebuild -version 2>/dev/null | head -1 || echo 'not found')" | |
| echo "Swift: $(swift --version 2>/dev/null | head -1 || echo 'not found')" | |
| echo "CPU: $(sysctl -n machdep.cpu.brand_string)" | |
| echo "Cores: $(sysctl -n hw.logicalcpu)" | |
| echo "RAM: $(($(sysctl -n hw.memsize) / 1073741824)) GB" | |
| - name: Checkout (with submodules) | |
| uses: actions/checkout@v4 | |
| with: | |
| submodules: false | |
| - name: Restore vendor submodules | |
| # Avoids network fetches to GitHub for whisper.cpp and llama.cpp on every run. | |
| # The UDM Pro NAT drops long-lived connections that cause recursive submodule | |
| # checkout to hang indefinitely. Instead we cache the source trees on the runner | |
| # at ~/.whiskey-vendor-cache/ and restore from disk, only falling back to the | |
| # network when the expected commit has changed (i.e. after a Vendor bump). | |
| run: | | |
| VENDOR_CACHE="${HOME}/.whiskey-vendor-cache" | |
| mkdir -p "$VENDOR_CACHE" | |
| for MODULE in whisper.cpp llama.cpp; do | |
| EXPECTED=$(git ls-tree HEAD "Vendor/${MODULE}" | awk '{print $3}') | |
| CACHE_COMMIT_FILE="${VENDOR_CACHE}/${MODULE}.commit" | |
| CACHED_COMMIT="" | |
| if [ -f "$CACHE_COMMIT_FILE" ]; then | |
| CACHED_COMMIT=$(cat "$CACHE_COMMIT_FILE") | |
| fi | |
| echo "[vendor] ${MODULE}: expected=${EXPECTED} cached=${CACHED_COMMIT:-none}" | |
| if [ -d "${VENDOR_CACHE}/${MODULE}" ] && [ "$EXPECTED" = "$CACHED_COMMIT" ]; then | |
| echo "[vendor] Cache hit for ${MODULE} — restoring from disk" | |
| rsync -a "${VENDOR_CACHE}/${MODULE}/" "Vendor/${MODULE}/" | |
| else | |
| echo "[vendor] Cache miss for ${MODULE} — fetching from GitHub" | |
| git submodule update --init --recursive "Vendor/${MODULE}" | |
| rsync -a "Vendor/${MODULE}/" "${VENDOR_CACHE}/${MODULE}/" | |
| echo "$EXPECTED" > "$CACHE_COMMIT_FILE" | |
| fi | |
| done | |
| git submodule init | |
| - name: Restore local SPM cache | |
| # ~/whiskey-spm-cache lives outside the workspace so it survives | |
| # git clean from concurrent SwiftLint/Code Quality jobs. On the first | |
| # run the directory won't exist and the build is cold; subsequent runs | |
| # are incremental (~30s instead of 14+ min). | |
| # | |
| # After cache restore, touch all vendor C/C++ sources to match build.db | |
| # mtime. git clean resets source timestamps to "now" which makes SPM | |
| # think vendor files are dirty and recompiles all 185 of them (~10 min). | |
| # Aligning timestamps tells SPM the vendor files haven't changed. | |
| run: | | |
| CACHE_DIR="${HOME}/.whiskey-spm-cache" | |
| if [ -d "$CACHE_DIR" ]; then | |
| echo "Restoring local SPM cache from $CACHE_DIR" | |
| rsync -a "$CACHE_DIR/" .build/ | |
| BUILD_DB=".build/arm64-apple-macosx/debug/build.db" | |
| if [ -f "$BUILD_DB" ]; then | |
| echo "Aligning vendor source timestamps to build.db to prevent full recompile" | |
| find Vendor/ -name "*.c" -o -name "*.cpp" -o -name "*.h" -o -name "*.m" | xargs touch -r "$BUILD_DB" | |
| fi | |
| else | |
| echo "No local SPM cache — cold build" | |
| fi | |
| - name: Install SQLCipher | |
| run: /opt/homebrew/bin/brew install sqlcipher 2>&1 || /opt/homebrew/bin/brew upgrade sqlcipher 2>&1 || true | |
| - name: Build (debug + tests) | |
| # Reserve 2 cores for the runner's .NET renewal timer. Using all cores | |
| # starves the timer thread, causing token renewal to fire after the | |
| # 10-min window expires — the server then rejects CompleteJob with 404. | |
| run: | | |
| NCPU=$(sysctl -n hw.logicalcpu) | |
| BUILD_JOBS=$(( NCPU > 2 ? NCPU - 2 : 1 )) | |
| while true; do echo "[heartbeat] $(date -u +%H:%M:%SZ)"; sleep 30; done & | |
| HPID=$! | |
| swift build -c debug --build-tests --jobs "$BUILD_JOBS" 2>&1 | |
| STATUS=$? | |
| kill $HPID 2>/dev/null || true | |
| exit $STATUS | |
| - name: Save local SPM cache | |
| # --update skips files where the destination is already up-to-date, | |
| # avoiding re-copying 700MB+ of timestamp-only changes after each build. | |
| if: success() || failure() | |
| run: | | |
| CACHE_DIR="${HOME}/.whiskey-spm-cache" | |
| echo "Saving SPM cache to $CACHE_DIR" | |
| rsync -a --update .build/ "$CACHE_DIR/" | |
| - name: Save vendor cache | |
| # Persist any newly-fetched submodule source trees back to disk so the | |
| # next run can skip the network entirely. Runs on failure too so a | |
| # partial submodule fetch still warms the cache for subsequent runs. | |
| if: success() || failure() | |
| run: | | |
| VENDOR_CACHE="${HOME}/.whiskey-vendor-cache" | |
| mkdir -p "$VENDOR_CACHE" | |
| for MODULE in whisper.cpp llama.cpp; do | |
| if [ -d "Vendor/${MODULE}/.git" ] || [ -f "Vendor/${MODULE}/.git" ]; then | |
| echo "[vendor] Saving ${MODULE} to cache" | |
| rsync -a "Vendor/${MODULE}/" "${VENDOR_CACHE}/${MODULE}/" | |
| git ls-tree HEAD "Vendor/${MODULE}" | awk '{print $3}' > "${VENDOR_CACHE}/${MODULE}.commit" | |
| fi | |
| done | |
| - name: Run tests | |
| # --skip-build: binary was compiled by "Build (debug + tests)" above. | |
| # Without this, swift test rebuilds all test targets (~11 min) even | |
| # though --build-tests already compiled everything. | |
| run: swift test --skip-build -c debug --filter WhisKeyCoreTests 2>&1 | |
| # --------------------------------------------------------------------------- | |
| # Job: build-release | |
| # Depends on build-and-test. Runs in its own job so the release build | |
| # gets a fresh runner session — separating long jobs reduces the chance | |
| # of a broker reconnect overlapping with job teardown. | |
| # Reuses ~/.whiskey-spm-cache saved by build-and-test (same machine). | |
| # --------------------------------------------------------------------------- | |
| build-release: | |
| name: Build (release) | |
| runs-on: [self-hosted, macOS] | |
| needs: build-and-test | |
| if: github.event_name == 'push' && github.ref == 'refs/heads/main' | |
| steps: | |
| - name: Checkout (with submodules) | |
| uses: actions/checkout@v4 | |
| with: | |
| submodules: false | |
| - name: Restore vendor submodules | |
| # Release builds always run on main where the cache should be warm from | |
| # build-and-test. Restore unconditionally from disk; fall back to network | |
| # only if the cache is absent (first ever run or manual cache purge). | |
| run: | | |
| VENDOR_CACHE="${HOME}/.whiskey-vendor-cache" | |
| mkdir -p "$VENDOR_CACHE" | |
| for MODULE in whisper.cpp llama.cpp; do | |
| EXPECTED=$(git ls-tree HEAD "Vendor/${MODULE}" | awk '{print $3}') | |
| CACHE_COMMIT_FILE="${VENDOR_CACHE}/${MODULE}.commit" | |
| CACHED_COMMIT="" | |
| if [ -f "$CACHE_COMMIT_FILE" ]; then | |
| CACHED_COMMIT=$(cat "$CACHE_COMMIT_FILE") | |
| fi | |
| echo "[vendor] ${MODULE}: expected=${EXPECTED} cached=${CACHED_COMMIT:-none}" | |
| if [ -d "${VENDOR_CACHE}/${MODULE}" ] && [ "$EXPECTED" = "$CACHED_COMMIT" ]; then | |
| echo "[vendor] Cache hit for ${MODULE} — restoring from disk" | |
| rsync -a "${VENDOR_CACHE}/${MODULE}/" "Vendor/${MODULE}/" | |
| else | |
| echo "[vendor] Cache miss for ${MODULE} — fetching from GitHub (cold)" | |
| git submodule update --init --recursive "Vendor/${MODULE}" | |
| rsync -a "Vendor/${MODULE}/" "${VENDOR_CACHE}/${MODULE}/" | |
| echo "$EXPECTED" > "$CACHE_COMMIT_FILE" | |
| fi | |
| done | |
| git submodule init | |
| - name: Restore release SPM cache | |
| # ~/.whiskey-spm-cache-release persists release-configuration artifacts | |
| # (.build/release/) across runs. Debug artifacts (from build-and-test) | |
| # are in a separate cache and not useful here. First run is cold (~11 min); | |
| # subsequent runs are warm (~2 min) and complete well within the | |
| # 10-minute broker session window. | |
| run: | | |
| CACHE_DIR="${HOME}/.whiskey-spm-cache-release" | |
| if [ -d "$CACHE_DIR" ]; then | |
| echo "Restoring release SPM cache from $CACHE_DIR" | |
| rsync -a "$CACHE_DIR/" .build/ | |
| else | |
| echo "No release SPM cache — cold build" | |
| fi | |
| - name: Install SQLCipher | |
| run: /opt/homebrew/bin/brew install sqlcipher 2>&1 || /opt/homebrew/bin/brew upgrade sqlcipher 2>&1 || true | |
| - name: Build (release) | |
| run: | | |
| NCPU=$(sysctl -n hw.logicalcpu) | |
| while true; do echo "[heartbeat] $(date -u +%H:%M:%SZ)"; sleep 30; done & | |
| HPID=$! | |
| swift build -c release --jobs "$NCPU" 2>&1 | |
| STATUS=$? | |
| kill $HPID 2>/dev/null || true | |
| exit $STATUS | |
| - name: Save release SPM cache | |
| if: success() || failure() | |
| run: | | |
| CACHE_DIR="${HOME}/.whiskey-spm-cache-release" | |
| echo "Saving release SPM cache to $CACHE_DIR" | |
| rsync -a .build/ "$CACHE_DIR/" | |
| - name: Save vendor cache | |
| if: success() || failure() | |
| run: | | |
| VENDOR_CACHE="${HOME}/.whiskey-vendor-cache" | |
| mkdir -p "$VENDOR_CACHE" | |
| for MODULE in whisper.cpp llama.cpp; do | |
| if [ -d "Vendor/${MODULE}/.git" ] || [ -f "Vendor/${MODULE}/.git" ]; then | |
| echo "[vendor] Saving ${MODULE} to cache" | |
| rsync -a "Vendor/${MODULE}/" "${VENDOR_CACHE}/${MODULE}/" | |
| git ls-tree HEAD "Vendor/${MODULE}" | awk '{print $3}' > "${VENDOR_CACHE}/${MODULE}.commit" | |
| fi | |
| done | |
| # --------------------------------------------------------------------------- | |
| # Job: swiftlint | |
| # Blocks PR merge. Runs SwiftLint with .swiftlint.yml config. | |
| # Annotates violations inline on the PR diff. | |
| # --------------------------------------------------------------------------- | |
| swiftlint: | |
| name: SwiftLint | |
| runs-on: [self-hosted, macOS] | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Install SwiftLint | |
| run: /opt/homebrew/bin/swiftlint --version 2>/dev/null || /opt/homebrew/bin/brew install swiftlint | |
| - name: Run SwiftLint | |
| # --strict turns warnings into errors, blocking the PR. | |
| run: /opt/homebrew/bin/swiftlint lint --strict --config .swiftlint.yml 2>&1 | |
| # --------------------------------------------------------------------------- | |
| # Job: code-quality | |
| # Advisory (non-blocking on push to main, blocking on PRs). | |
| # Checks TODO/FIXME density and file organization. | |
| # --------------------------------------------------------------------------- | |
| code-quality: | |
| name: Code Quality Checks | |
| runs-on: [self-hosted, macOS] | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: TODO/FIXME density | |
| run: | | |
| TODO_COUNT=$(grep -rn "TODO\|FIXME" Sources/ --include="*.swift" | wc -l | tr -d ' ') | |
| echo "Found $TODO_COUNT TODO/FIXME markers in Sources/" | |
| if [ "$TODO_COUNT" -gt 30 ]; then | |
| echo "::error::TODO/FIXME count ($TODO_COUNT) exceeds threshold (30). Triage before merging." | |
| exit 1 | |
| elif [ "$TODO_COUNT" -gt 15 ]; then | |
| echo "::warning::TODO/FIXME count ($TODO_COUNT) is elevated. Consider triaging soon." | |
| fi | |
| echo "Quality check passed ($TODO_COUNT markers)." | |
| - name: No force-push markers | |
| # Catch leftover conflict markers that escape SwiftLint | |
| run: | | |
| if grep -rn "<<<<<<\|>>>>>>\|=======" Sources/ Tests/ --include="*.swift"; then | |
| echo "::error::Merge conflict markers found in source files." | |
| exit 1 | |
| fi | |
| echo "No conflict markers found." | |
| - name: Test file parity check | |
| # Warn if a Sources/ module has no corresponding test file. | |
| run: | | |
| MISSING=0 | |
| for dir in Sources/WhisKeyCore/*/; do | |
| module=$(basename "$dir") | |
| test_file="Tests/WhisKeyCoreTests/${module}Tests.swift" | |
| if [ ! -f "$test_file" ]; then | |
| echo "::warning::No test file for module: $module (expected $test_file)" | |
| MISSING=$((MISSING + 1)) | |
| fi | |
| done | |
| echo "Modules without test files: $MISSING" | |
| # Don't fail — some modules (e.g. Models) legitimately share test files. |