fix(ci): resolve siblings via go.work replace (not use) + require yaad #492
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
| # Strict CI pipeline for GrayCodeAI Go repos. | |
| # All jobs must pass — no continue-on-error except where explicitly noted. | |
| name: CI | |
| on: | |
| push: | |
| branches: [main] | |
| pull_request: | |
| branches: [main] | |
| permissions: | |
| contents: read | |
| security-events: write | |
| pull-requests: read | |
| concurrency: | |
| group: ci-${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: true | |
| env: | |
| GO_VERSION: "1.26.4" | |
| # GrayCodeAI sibling modules are resolved from the local external/ submodules via | |
| # go.work; their go.mod require versions (v0.1.0) intentionally do not match the | |
| # frozen public proxy/sumdb snapshot, so bypass the proxy + checksum DB for them. | |
| GOPRIVATE: "github.com/GrayCodeAI/*" | |
| GONOSUMDB: "github.com/GrayCodeAI/*" | |
| GONOSUMCHECK: "1" | |
| jobs: | |
| # ------------------------------------------------------------------------- | |
| # 1. Format — gofumpt + goimports must be clean (zero tolerance). | |
| # ------------------------------------------------------------------------- | |
| format: | |
| name: format | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 | |
| with: | |
| go-version: ${{ env.GO_VERSION }} | |
| cache: true | |
| - name: gofumpt | |
| run: | | |
| go install mvdan.cc/gofumpt@v0.7.0 | |
| out=$(gofumpt -l .) | |
| if [ -n "$out" ]; then | |
| echo "::error::gofumpt would reformat:" | |
| echo "$out" | head -20 | |
| exit 1 | |
| fi | |
| - name: goimports | |
| run: | | |
| go install golang.org/x/tools/cmd/goimports@v0.30.0 | |
| out=$(goimports -l .) | |
| if [ -n "$out" ]; then | |
| echo "::error::goimports would reformat:" | |
| echo "$out" | head -20 | |
| exit 1 | |
| fi | |
| # ------------------------------------------------------------------------- | |
| # 2. Module hygiene — tidy, verify (hawk + external ecosystem repos via go.work). | |
| # ------------------------------------------------------------------------- | |
| module: | |
| name: module hygiene | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| - uses: ./.github/actions/checkout-eyrie | |
| with: | |
| ref: ${{ github.head_ref || github.ref_name }} | |
| - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 | |
| with: | |
| go-version: ${{ env.GO_VERSION }} | |
| cache: true | |
| - name: go work sync + module consistency | |
| run: | | |
| # Ecosystem repos are external checkouts under hawk/external. go mod tidy can mis-resolve | |
| # workspace modules here; go work sync is the supported workspace hygiene step. | |
| go work sync | |
| go build -mod=readonly -o /dev/null ./cmd/hawk | |
| if ! git diff --quiet -- go.mod go.sum go.work go.work.sum; then | |
| echo "::error::go.mod / go.sum / go.work files out of date — run 'go work sync' locally and commit" | |
| git diff -- go.mod go.sum go.work go.work.sum | |
| exit 1 | |
| fi | |
| - name: go mod verify | |
| run: go mod verify | |
| - name: workspace points at external checkouts | |
| run: | | |
| for module in eyrie inspect sight tok trace yaad; do | |
| if ! grep -q "./external/${module}" go.work; then | |
| echo "::error::go.work must include ./external/${module}." | |
| cat go.work | |
| exit 1 | |
| fi | |
| done | |
| # ------------------------------------------------------------------------- | |
| # 3. Vet + static analysis — compiler-level correctness. | |
| # ------------------------------------------------------------------------- | |
| vet: | |
| name: vet | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| - uses: ./.github/actions/checkout-eyrie | |
| with: | |
| ref: ${{ github.head_ref || github.ref_name }} | |
| - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 | |
| with: | |
| go-version: ${{ env.GO_VERSION }} | |
| cache: true | |
| - name: go vet | |
| run: go vet ./... | |
| # ------------------------------------------------------------------------- | |
| # 4. Lint — golangci-lint with project-specific config. | |
| # ------------------------------------------------------------------------- | |
| lint: | |
| name: lint | |
| runs-on: ubuntu-latest | |
| needs: [format, vet] | |
| steps: | |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| - uses: ./.github/actions/checkout-eyrie | |
| with: | |
| ref: ${{ github.head_ref || github.ref_name }} | |
| - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 | |
| with: | |
| go-version: ${{ env.GO_VERSION }} | |
| cache: true | |
| - name: Run golangci-lint | |
| run: | | |
| go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.1.0 | |
| golangci-lint run --timeout=5m | |
| # ------------------------------------------------------------------------- | |
| # 5. Tests — race detector, coverage threshold, test shuffling. | |
| # ------------------------------------------------------------------------- | |
| test: | |
| name: test (race + coverage) | |
| runs-on: ubuntu-latest | |
| needs: [format, vet] | |
| steps: | |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| - uses: ./.github/actions/checkout-eyrie | |
| with: | |
| ref: ${{ github.head_ref || github.ref_name }} | |
| - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 | |
| with: | |
| go-version: ${{ env.GO_VERSION }} | |
| cache: true | |
| - name: Test with race detector | |
| run: go test ./... -race -count=1 -shuffle=on -coverprofile=coverage.out -covermode=atomic -timeout=300s -skip=TestDefaultSkillDirsCrossAgent | |
| - name: Coverage summary | |
| run: | | |
| coverage=$(go tool cover -func=coverage.out | grep total | awk '{print $3}' | tr -d '%' | tail -1) | |
| echo "Coverage: ${coverage}%" | |
| echo "COVERAGE=${coverage}" >> "$GITHUB_ENV" | |
| - name: Coverage threshold (minimum 50%) | |
| run: | | |
| if (( $(echo "${COVERAGE} < 60" | bc -l) )); then | |
| echo "::error::Coverage ${COVERAGE}% is below minimum 60%" | |
| exit 1 | |
| fi | |
| - name: Upload coverage | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: coverage-${{ github.job }} | |
| path: coverage.out | |
| retention-days: 30 | |
| - name: Enforce test skip policy | |
| continue-on-error: true | |
| run: | | |
| # Find all t.Skip() calls without a tracking issue comment | |
| # Policy: t.Skip() should have a comment with a GitHub issue link on the same or previous line | |
| violations="" | |
| while IFS= read -r line; do | |
| file=$(echo "$line" | cut -d: -f1) | |
| lineno=$(echo "$line" | cut -d: -f2) | |
| # Check previous line for issue link | |
| prev_line=$((lineno - 1)) | |
| if [ "$prev_line" -gt 0 ]; then | |
| prev_content=$(sed -n "${prev_line}p" "$file") | |
| if echo "$prev_content" | grep -qE '(github\.com/.*issues/|#\d+|TODO|FIXME|HACK)'; then | |
| continue | |
| fi | |
| fi | |
| # Check same line for issue link | |
| content=$(sed -n "${lineno}p" "$file") | |
| if echo "$content" | grep -qE '(github\.com/.*issues/|#\d+|TODO|FIXME|HACK)'; then | |
| continue | |
| fi | |
| violations="${violations}\n${file}:${lineno}: t.Skip() without tracking issue" | |
| done < <(grep -rn 't\.Skip(' --include='*_test.go' . --exclude-dir=external 2>/dev/null | grep -v '// nolint' || true) | |
| if [ -n "$violations" ]; then | |
| echo "::warning::Found t.Skip() without tracking issue:${violations}" | |
| echo "" | |
| echo "Policy: Every t.Skip() should have a comment linking to a GitHub issue." | |
| echo "Example:" | |
| echo " // TODO: https://github.com/GrayCodeAI/hawk/issues/123" | |
| echo " t.Skip(\"not yet implemented\")" | |
| fi | |
| # ------------------------------------------------------------------------- | |
| # 6. Security — vulnerability scan + secret detection. | |
| # ------------------------------------------------------------------------- | |
| security: | |
| name: security | |
| runs-on: ubuntu-latest | |
| needs: [format, vet] | |
| steps: | |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| - uses: ./.github/actions/checkout-eyrie | |
| with: | |
| ref: ${{ github.head_ref || github.ref_name }} | |
| - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 | |
| with: | |
| go-version: ${{ env.GO_VERSION }} | |
| cache: true | |
| - name: govulncheck | |
| run: | | |
| go install golang.org/x/vuln/cmd/govulncheck@v1.1.4 | |
| govulncheck ./... | |
| - name: gosec (report only) | |
| continue-on-error: true | |
| run: | | |
| go install github.com/securego/gosec/v2/cmd/gosec@v2.22.4 | |
| gosec -exclude=G104,G703,G704,G101,G107,G112,G114,G115,G201,G202,G203,G204,G301,G302,G304,G305,G306,G307,G401,G402,G403,G404,G501,G502,G503,G504,G505,G601,G602 -confidence=medium -severity=high ./... | |
| # ------------------------------------------------------------------------- | |
| # 7. Secret scan — detect leaked API keys, tokens, credentials. | |
| # ------------------------------------------------------------------------- | |
| secrets: | |
| name: secrets | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| - uses: trufflesecurity/trufflehog@0fa069c12f0c7baf431041cd1e564a9c5058846c # main 2026-05-18 | |
| with: | |
| extra_args: --only-verified | |
| # ------------------------------------------------------------------------- | |
| # 8. Markdown lint — validate documentation quality. | |
| # ------------------------------------------------------------------------- | |
| markdown: | |
| name: markdown | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| - name: Run markdownlint-cli2 | |
| run: | | |
| npm install -g markdownlint-cli2 | |
| printf '%s\n' '{"config":{"default":true,"line-length":false,"no-inline-html":false,"first-line-h1":false,"no-duplicate-heading":false,"no-emphasis-as-heading":false,"blanks-around-headings":false,"blanks-around-lists":false,"blanks-around-fences":false,"fenced-code-language":false,"table-column-style":false,"no-space-in-emphasis":false,"ol-prefix":false,"link-fragments":false,"blanks-around-tables":false,"table-column-count":false,"single-trailing-newline":false}}' > .markdownlint-cli2.jsonc | |
| markdownlint-cli2 '**/*.md' | |
| # ------------------------------------------------------------------------- | |
| # Dead code detection. | |
| # ------------------------------------------------------------------------- | |
| deadcode: | |
| name: deadcode | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd | |
| - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c | |
| with: | |
| go-version: "1.26.4" | |
| cache: true | |
| - name: deadcode | |
| run: | | |
| go install golang.org/x/tools/cmd/deadcode@v0.30.0 | |
| deadcode ./... 2>&1 | head -50 | |
| # ------------------------------------------------------------------------- | |
| # Duplication detection — jscpd. | |
| # ------------------------------------------------------------------------- | |
| jscpd: | |
| name: duplication | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 | |
| - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 | |
| with: | |
| node-version: '24' | |
| - name: jscpd | |
| run: | | |
| npx jscpd --min-lines 5 --min-tokens 50 --reporters console --blame . 2>&1 | head -50 | |
| # ------------------------------------------------------------------------- | |
| # 9. Cross-platform build matrix — zero CGO, all targets. | |
| # ------------------------------------------------------------------------- | |
| build: | |
| name: build (${{ matrix.goos }}/${{ matrix.goarch }}) | |
| runs-on: ubuntu-latest | |
| needs: [format, lint, test, security] | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| goos: [linux, darwin, windows] | |
| goarch: [amd64, arm64] | |
| exclude: | |
| - goos: windows | |
| goarch: arm64 | |
| steps: | |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| - uses: ./.github/actions/checkout-eyrie | |
| with: | |
| ref: ${{ github.head_ref || github.ref_name }} | |
| - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 | |
| with: | |
| go-version: ${{ env.GO_VERSION }} | |
| cache: true | |
| - name: Build | |
| env: | |
| GOOS: ${{ matrix.goos }} | |
| GOARCH: ${{ matrix.goarch }} | |
| CGO_ENABLED: "0" | |
| run: go build -trimpath -v ./... | |
| - name: Binary size check (linux/amd64 only) | |
| if: matrix.goos == 'linux' && matrix.goarch == 'amd64' | |
| run: | | |
| size=$(go build -trimpath -o /tmp/hawk-bin ./cmd/hawk && wc -c < /tmp/hawk-bin) | |
| size_mb=$((size / 1024 / 1024)) | |
| echo "Binary size: ${size_mb}MB" | |
| if [ "$size_mb" -gt 100 ]; then | |
| echo "::warning::Binary size ${size_mb}MB exceeds 100MB threshold" | |
| fi | |
| rm -f /tmp/hawk-bin | |
| # ------------------------------------------------------------------------- | |
| # Fuzz — short corpus runs to catch panics in fuzz targets. | |
| # ------------------------------------------------------------------------- | |
| fuzz: | |
| name: fuzz (60s) | |
| runs-on: ubuntu-latest | |
| needs: [test] | |
| steps: | |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| - uses: ./.github/actions/checkout-eyrie | |
| with: | |
| ref: ${{ github.head_ref || github.ref_name }} | |
| - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 | |
| with: | |
| go-version: ${{ env.GO_VERSION }} | |
| cache: true | |
| - name: Run fuzz targets | |
| run: | | |
| go test -fuzz=FuzzScanForAIComments -fuzztime=60s ./cmd/... || true | |
| go test -fuzz=FuzzValidateSettings -fuzztime=60s ./internal/config/... || true | |
| go test -fuzz=FuzzIsSuspicious -fuzztime=60s ./internal/tool/... || true | |
| go test -fuzz=FuzzIsSafeGitCommit -fuzztime=60s ./internal/tool/... || true | |
| go test -fuzz=FuzzParseMessage -fuzztime=60s ./internal/session/... || true | |
| go test -fuzz=FuzzParseSessionMeta -fuzztime=60s ./internal/session/... || true | |
| # ------------------------------------------------------------------------- | |
| # 10. Smoke — build hawk and verify ecosystem CLI wiring. | |
| # ------------------------------------------------------------------------- | |
| smoke: | |
| name: smoke | |
| runs-on: ubuntu-latest | |
| needs: [format, vet] | |
| steps: | |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| - uses: ./.github/actions/checkout-eyrie | |
| with: | |
| ref: ${{ github.head_ref || github.ref_name }} | |
| - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 | |
| with: | |
| go-version: ${{ env.GO_VERSION }} | |
| cache: true | |
| - name: smoke-hawk.sh | |
| run: ./scripts/smoke-hawk.sh |