Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .claude/worktrees/plan-review-edits
Submodule plan-review-edits deleted from 98a386
69 changes: 54 additions & 15 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,44 +3,83 @@ name: CI
on:
push:
pull_request:
workflow_call:
workflow_dispatch:

permissions:
contents: read

jobs:
build-test-lint:
name: Build, test, and lint
build:
name: build
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2

- name: Set up Go
uses: actions/setup-go@v5
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
with:
go-version-file: go.mod
cache: true

- name: Verify modules
run: go mod verify

- name: Build
run: go build ./...

- name: Vet
run: go vet ./...
test-race:
name: test-race
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2

- name: Set up Go
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
with:
go-version-file: go.mod
cache: true

- name: Test with race detector and coverage
run: go test -race -count=1 -covermode=atomic -coverpkg=./... -coverprofile=coverage.out ./...

- name: Enforce coverage floor
shell: bash
run: |
coverage="$(go tool cover -func=coverage.out | awk '$1 == "total:" {gsub("%", "", $3); print $3}')"
awk -v coverage="${coverage}" 'BEGIN { if (coverage + 0 < 88.1) { printf "coverage %.1f%% is below 88.1%%\n", coverage; exit 1 } }'

lint:
name: lint
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2

- name: Set up Go
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
with:
go-version-file: go.mod
cache: true

- name: Check gofmt
shell: bash
run: |
unformatted="$(gofmt -l . | grep -v '^vendor/' || true)"
if [ -n "$unformatted" ]; then
echo "The following files are not gofmt-clean:"
echo "$unformatted"
exit 1
fi
test -z "${unformatted}" || { printf 'The following files are not gofmt-clean:\n%s\n' "${unformatted}"; exit 1; }

- name: Vet
run: go vet ./...

- name: Test
run: go test -race -cover ./...
- name: Staticcheck
run: |
go install honnef.co/go/tools/cmd/staticcheck@2025.1.1
staticcheck ./...

- name: Lint
uses: golangci/golangci-lint-action@9fae48acfc02a90574d7c304a1758ef9895495fa # v7
- name: GolangCI-Lint
uses: golangci/golangci-lint-action@9fae48acfc02a90574d7c304a1758ef9895495fa # v7.0.1
with:
version: v2.5.0
install-mode: goinstall
Expand Down
40 changes: 40 additions & 0 deletions .github/workflows/fuzz.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
name: Scheduled fuzzing

on:
schedule:
- cron: "41 4 * * 2"
workflow_dispatch:

permissions:
contents: read

jobs:
fuzz:
name: fuzz-${{ matrix.target }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- package: ./internal/session
target: FuzzFrameDecoding
- package: ./internal/vterm
target: FuzzTerminalInput
- package: ./internal/displaytext
target: FuzzSanitize
- package: ./internal/session
target: FuzzAttachFiltering
- package: ./internal/session
target: FuzzStateDecoding
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0

- name: Set up Go
uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6.5.0
with:
go-version-file: go.mod
cache: true

- name: Fuzz for 30 seconds
run: go test "${{ matrix.package }}" -run=^$ -fuzz="^${{ matrix.target }}$" -fuzztime=30s
86 changes: 86 additions & 0 deletions .github/workflows/release-smoke.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
name: Release smoke

on:
push:
branches: [main]
pull_request:
workflow_dispatch:

permissions:
contents: read

concurrency:
group: release-smoke-${{ github.event.pull_request.head.ref || github.ref_name }}
cancel-in-progress: true

jobs:
snapshot:
name: GoReleaser snapshot
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
fetch-depth: 0

- name: Set up Go
uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6.5.0
with:
go-version-file: go.mod
cache: true

- name: Validate GoReleaser configuration
uses: goreleaser/goreleaser-action@f06c13b6b1a9625abc9e6e439d9c05a8f2190e94 # v7.2.3
with:
distribution: goreleaser
version: v2.17.0
args: check

- name: Build snapshot archives
uses: goreleaser/goreleaser-action@f06c13b6b1a9625abc9e6e439d9c05a8f2190e94 # v7.2.3
with:
distribution: goreleaser
version: v2.17.0
args: release --snapshot --clean --skip=sign,sbom

- name: Verify archive names and contents
shell: bash
run: |
mapfile -t archives < <(find dist -maxdepth 1 -type f -name 'uam_*.tar.gz' -print | sort)
test "${#archives[@]}" -eq 4
for archive in "${archives[@]}"; do
basename "${archive}" | grep -Eq '^uam_.+_(linux|darwin)_(amd64|arm64)\.tar\.gz$'
tar -tzf "${archive}" | grep -Eq '(^|/)uam$'
done
linux_archive="$(find dist -maxdepth 1 -type f -name 'uam_*_linux_amd64.tar.gz' -print -quit)"
test -n "${linux_archive}"
mkdir -p /tmp/uam-release-smoke
tar -xzf "${linux_archive}" -C /tmp/uam-release-smoke
binary="$(find /tmp/uam-release-smoke -type f -name uam -print -quit)"
test -n "${binary}"
"${binary}" version | grep -E '.+'

darwin-native:
name: Darwin-native lifecycle and version smoke
runs-on: macos-14
timeout-minutes: 8
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0

- name: Set up Go
uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6.5.0
with:
go-version-file: go.mod
cache: true

- name: Run native session lifecycle tests
run: go test -race -count=1 -timeout=4m ./internal/session

- name: Run stop, restart, and resume tests
run: go test -race -count=1 -timeout=2m ./internal/app

- name: Build and run version command
run: |
go build -o "${RUNNER_TEMP}/uam" ./cmd/uam
"${RUNNER_TEMP}/uam" version | grep -E '.+'
57 changes: 49 additions & 8 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,63 @@ on:
- "v*"

permissions:
contents: write
id-token: write
packages: read
contents: read

jobs:
goreleaser:
name: GoReleaser
quality:
name: Quality verification
uses: ./.github/workflows/ci.yml

security:
name: Security verification
uses: ./.github/workflows/security.yml

ancestry:
name: Verify tag is on main
runs-on: ubuntu-latest
steps:
- name: Checkout tagged commit
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
fetch-depth: 0

- name: Verify tagged commit is an ancestor of main
run: |
git fetch --no-tags origin main
git merge-base --is-ancestor "${GITHUB_SHA}" origin/main

release-config:
name: Validate release configuration
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
fetch-depth: 0

- name: Validate GoReleaser configuration
uses: goreleaser/goreleaser-action@e435ccd777264be153ace6237001ef4d979d3a7a # v6.4.0
with:
version: "~> v2"
args: check

publish:
name: Publish release
needs: [quality, security, ancestry, release-config]
runs-on: ubuntu-latest
environment: release
permissions:
contents: write
id-token: write
packages: read
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
fetch-depth: 0

- name: Set up Go
uses: actions/setup-go@v5
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
with:
go-version-file: go.mod
cache: true
Expand All @@ -33,7 +74,7 @@ jobs:
uses: sigstore/cosign-installer@dc72c7d5c4d10cd6bcb8cf6e3fd625a9e5e537da # v3.7.0

- name: Run GoReleaser
uses: goreleaser/goreleaser-action@e435ccd777264be153ace6237001ef4d979d3a7a # v6
uses: goreleaser/goreleaser-action@e435ccd777264be153ace6237001ef4d979d3a7a # v6.4.0
with:
version: "~> v2"
args: release --clean
Expand Down
46 changes: 16 additions & 30 deletions .github/workflows/security.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,72 +4,58 @@ on:
push:
branches: [main]
pull_request:
branches: [main]
schedule:
- cron: "23 3 * * 1"
workflow_call:
workflow_dispatch:

permissions:
contents: read
actions: read
security-events: write
pull-requests: read

jobs:
govulncheck:
name: Go vulnerability check
name: govulncheck
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2

- name: Set up Go
uses: actions/setup-go@v5
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
with:
go-version-file: go.mod
cache: true

- name: Install govulncheck
run: go install golang.org/x/vuln/cmd/govulncheck@v1.3.0

- name: Run govulncheck
run: govulncheck ./...
run: |
go install golang.org/x/vuln/cmd/govulncheck@v1.3.0
govulncheck ./...

gosec:
name: Gosec static analysis
name: gosec
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2

- name: Set up Go
uses: actions/setup-go@v5
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
with:
go-version-file: go.mod
cache: true

- name: Install gosec
run: go install github.com/securego/gosec/v2/cmd/gosec@v2.26.1

- name: Run gosec
run: gosec -exclude-dir=.claude -exclude-dir=vendor -fmt=sarif -out=gosec.sarif ./...

- name: Upload gosec SARIF
if: always()
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: gosec.sarif
run: |
go install github.com/securego/gosec/v2/cmd/gosec@v2.26.1
gosec -exclude-dir=.claude -exclude-dir=vendor ./...

dependency-review:
name: Dependency review
name: dependency-review
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: read
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2

- name: Review dependency changes
uses: actions/dependency-review-action@v4
uses: actions/dependency-review-action@3c4e3dcb1aa7874d2c16be7d79418e9b7efd6261 # v4.8.2
Loading
Loading