Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
29 changes: 29 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Normalize line endings to LF in the repository and working tree. csdd runs on
# Windows/WSL where autocrlf otherwise makes the entire tree show as Modified and
# breaks gofmt/line-ending-sensitive checks on Windows CI. LF everywhere keeps
# manifest hashes, Go source, and shell hooks consistent across platforms.
* text=auto eol=lf

# Go, docs, shell hooks, and config are always LF.
*.go text eol=lf
*.mod text eol=lf
*.sum text eol=lf
*.sh text eol=lf
*.md text eol=lf
*.json text eol=lf
*.yml text eol=lf
*.yaml text eol=lf
*.tmpl text eol=lf
*.ts text eol=lf
*.mjs text eol=lf
*.js text eol=lf

# Binary assets must never be line-ending converted.
*.png binary
*.jpg binary
*.gif binary
*.ico binary
*.gz binary
*.zip binary
*.woff binary
*.woff2 binary
234 changes: 142 additions & 92 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,92 +1,142 @@
name: ci

# Test + validate gate. Runs on:
# - every pull request — validate changes before they can merge.
# - push to main — validate the merge result on the default branch, since the
# release workflow is now manual (workflow_dispatch) and no longer runs on
# push, so a merge would otherwise go unverified.
# - workflow_call — reusable, so the manual release pipeline requires this gate
# before building or publishing any binaries.
on:
push:
branches: [main]
pull_request:
workflow_call:

permissions:
contents: read

jobs:
test:
name: test & validate
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: actions/setup-go@v5
with:
go-version: '1.22'
cache: true

- name: gofmt
run: |
unformatted="$(gofmt -l .)"
if [ -n "$unformatted" ]; then
echo "::error::these files are not gofmt-clean:"
echo "$unformatted"
exit 1
fi

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

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

- name: test (race + coverage)
run: go test -race -coverprofile=coverage.out ./...

web:
name: web dashboard (build)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: '22'
cache: npm
cache-dependency-path: internal/web/frontend/package-lock.json

- name: install
run: npm --prefix internal/web/frontend ci

# The dashboard (internal/web/dist) is a build artifact, not committed.
# This verifies it still builds; the release pipeline builds it for real
# before cross-compiling so released binaries embed the UI.
- name: build dashboard
run: npm --prefix internal/web/frontend run build

mcp-server:
name: mcp-server (build & test)
runs-on: ubuntu-latest
defaults:
run:
working-directory: mcp-server
steps:
- uses: actions/checkout@v4

# Node 24: the unit tests are TypeScript run through node:test with native
# type stripping (Node >= 22.18). The published package ships compiled JS
# (dist/) and still supports Node >= 18 — see package.json "engines".
- uses: actions/setup-node@v4
with:
node-version: '24'
cache: npm
cache-dependency-path: mcp-server/package-lock.json

- name: install
run: npm ci

- name: test (build + node:test)
run: npm test
name: ci

# Test + validate gate. Runs on:
# - every pull request — validate changes before they can merge.
# - push to main — validate the merge result on the default branch, since the
# release workflow is now manual (workflow_dispatch) and no longer runs on
# push, so a merge would otherwise go unverified.
# - workflow_call — reusable, so the manual release pipeline requires this gate
# before building or publishing any binaries.
on:
push:
branches: [main]
pull_request:
workflow_call:

permissions:
contents: read

jobs:
test:
name: test (${{ matrix.os }})
# Windows is a first-class target and the tool is path/line-ending heavy, so
# exercise the suite on all three OSes. fail-fast is off so one platform's
# failure still lets the others report.
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1

- uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5.6.0
with:
# Build/test with a current, patched toolchain even though go.mod keeps
# its language directive at 1.22 for install-compatibility. Do NOT switch
# this to go-version-file: go.mod.
go-version: '1.25'
cache: true

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

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

# The race detector needs a C toolchain + external linker, which the
# Windows runners don't provide out of the box, so run race+coverage on
# Linux/macOS and a plain (still cgo-free) test pass on Windows.
- name: test (race + coverage)
if: runner.os != 'Windows'
run: go test -race -coverprofile=coverage.out ./...

- name: test (coverage, no race)
if: runner.os == 'Windows'
# Force bash so the coverprofile arg parses like Linux/macOS; the Windows
# runner's default PowerShell mangles `-coverprofile=coverage.out ./...`
# into a bogus `.out` package pattern.
shell: bash
run: go test -coverprofile=coverage.out ./...

lint:
name: lint & vulncheck
# gofmt and the static analyzers only need to run once; keep them on Linux
# (the Windows runner checks code out with CRLF endings, which gofmt -l would
# flag on every file).
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1

- uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5.6.0
with:
go-version: '1.25'
cache: true

- name: gofmt
run: |
unformatted="$(gofmt -l .)"
if [ -n "$unformatted" ]; then
echo "::error::these files are not gofmt-clean:"
echo "$unformatted"
exit 1
fi

# Scan the module (and its dependencies) for known-vulnerable, *reachable*
# code paths. This is meant to fail the build — shipping binaries built on a
# flagged stdlib/dep is exactly what we're guarding against.
- name: govulncheck
run: |
go install golang.org/x/vuln/cmd/govulncheck@latest
"$(go env GOPATH)/bin/govulncheck" ./...

# golangci-lint runs the conservative set configured in .golangci.yml.
- name: golangci-lint
uses: golangci/golangci-lint-action@4afd733a84b1f43292c63897423277bb7f4313a9 # v8.0.0
with:
version: v2.12.2

web:
name: web dashboard (build)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1

- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: '22'
cache: npm
cache-dependency-path: internal/web/frontend/package-lock.json

- name: install
run: npm --prefix internal/web/frontend ci

# The dashboard (internal/web/dist) is a build artifact, not committed.
# This verifies it still builds; the release pipeline builds it for real
# before cross-compiling so released binaries embed the UI.
- name: build dashboard
run: npm --prefix internal/web/frontend run build

mcp-server:
name: mcp-server (build & test)
runs-on: ubuntu-latest
defaults:
run:
working-directory: mcp-server
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1

# Node 24: the unit tests are TypeScript run through node:test with native
# type stripping (Node >= 22.18). The published package ships compiled JS
# (dist/) and still supports Node >= 18 — see package.json "engines".
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: '24'
cache: npm
cache-dependency-path: mcp-server/package-lock.json

- name: install
run: npm ci

- name: test (build + node:test)
run: npm test
Loading
Loading