feat: start investigations from threads #30
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: | |
| push: | |
| branches: [main] | |
| permissions: | |
| contents: read | |
| jobs: | |
| test: | |
| name: Test (${{ matrix.os }}) | |
| runs-on: ${{ matrix.os }} | |
| timeout-minutes: 20 | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| os: [ubuntu-latest, macos-latest, windows-latest] | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - uses: actions/setup-go@v6 | |
| with: | |
| go-version-file: go.mod | |
| cache: true | |
| - run: go test -parallel=4 ./... | |
| validate-agents-md: | |
| name: Validate AGENTS.md | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Validate AGENTS.md references | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| errors=0 | |
| report_ok() { echo " OK: $1"; } | |
| report_err() { echo "::error file=AGENTS.md::$1"; errors=$((errors + 1)); } | |
| echo "::group::Checking markdown links in AGENTS.md" | |
| while IFS= read -r link; do | |
| [[ -z "$link" ]] && continue | |
| if [[ ! -e "$link" ]]; then | |
| report_err "Markdown link target missing: $link" | |
| else | |
| report_ok "$link" | |
| fi | |
| done < <(sed -n 's/.*\[[^]]*\](\([^)]*\)).*/\1/p' AGENTS.md | sort -u) | |
| echo "::endgroup::" | |
| echo "::group::Checking backtick-quoted file paths in AGENTS.md" | |
| while IFS= read -r path; do | |
| [[ -z "$path" ]] && continue | |
| [[ "$path" =~ ^https?:// ]] && continue | |
| [[ "$path" =~ ^(gofmt|go\ test|go\ vet|git\ ) ]] && continue | |
| if [[ ! -e "$path" ]]; then | |
| report_err "Backtick-quoted path missing: $path" | |
| else | |
| report_ok "$path" | |
| fi | |
| done < <(sed -n 's/.*`\([^`]*\)`.*/\1/p' AGENTS.md | grep -E '^(docs/|LICENSES|internal/|cmd/)' | sort -u) | |
| echo "::endgroup::" | |
| echo "::group::Checking directory references in AGENTS.md" | |
| while IFS= read -r dir; do | |
| [[ -z "$dir" ]] && continue | |
| if [[ ! -d "$dir" ]]; then | |
| report_err "Referenced directory missing: $dir" | |
| else | |
| report_ok "$dir" | |
| fi | |
| done < <(grep -oE '\b(internal/[a-zA-Z0-9_/]+|cmd/[a-zA-Z0-9_/]+)\b' AGENTS.md | sort -u) | |
| echo "::endgroup::" | |
| if [[ $errors -gt 0 ]]; then | |
| echo "::error::AGENTS.md validation failed with $errors error(s)" | |
| exit 1 | |
| fi | |
| echo "AGENTS.md validation passed" | |
| - name: Check AGENTS.md structure | |
| shell: bash | |
| run: | | |
| errors=0 | |
| if [[ ! -s AGENTS.md ]]; then | |
| echo "::error::AGENTS.md is missing or empty" | |
| exit 1 | |
| fi | |
| echo "AGENTS.md exists ($(wc -l < AGENTS.md) lines, $(wc -c < AGENTS.md) bytes)" | |
| if ! grep -q '^#' AGENTS.md; then | |
| echo "::error file=AGENTS.md::AGENTS.md has no section headers" | |
| errors=$((errors + 1)) | |
| fi | |
| if [[ $errors -gt 0 ]]; then | |
| exit 1 | |
| fi | |
| vet: | |
| name: Static checks | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - uses: actions/setup-go@v6 | |
| with: | |
| go-version-file: go.mod | |
| cache: true | |
| - name: Check formatting | |
| shell: bash | |
| run: test -z "$(gofmt -l .)" | |
| - run: go vet ./... | |
| golangci-lint: | |
| name: Go linters | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| fetch-depth: 0 | |
| - uses: actions/setup-go@v6 | |
| with: | |
| go-version-file: go.mod | |
| cache: true | |
| - name: Resolve lint comparison base | |
| id: lint-base | |
| shell: bash | |
| env: | |
| EVENT_NAME: ${{ github.event_name }} | |
| PR_BASE: ${{ github.event.pull_request.base.sha }} | |
| PUSH_BEFORE: ${{ github.event.before }} | |
| run: | | |
| base=$PUSH_BEFORE | |
| if [[ $EVENT_NAME == pull_request ]]; then | |
| base=$PR_BASE | |
| fi | |
| if [[ -z $base || $base =~ ^0+$ ]] || ! git cat-file -e "$base^{commit}"; then | |
| base=$(git rev-list --max-parents=0 HEAD) | |
| fi | |
| echo "sha=$base" >> "$GITHUB_OUTPUT" | |
| - uses: golangci/golangci-lint-action@v7 | |
| with: | |
| version: v2.12 | |
| args: --timeout=5m --new-from-rev=${{ steps.lint-base.outputs.sha }} | |
| check-large-files: | |
| name: Check file sizes | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| fetch-depth: 0 | |
| - name: Prevent growth of oversized Go files | |
| shell: bash | |
| env: | |
| EVENT_NAME: ${{ github.event_name }} | |
| PR_BASE: ${{ github.event.pull_request.base.sha }} | |
| PUSH_BEFORE: ${{ github.event.before }} | |
| run: | | |
| base=$PUSH_BEFORE | |
| if [[ $EVENT_NAME == pull_request ]]; then | |
| base=$PR_BASE | |
| fi | |
| if [[ -z $base || $base =~ ^0+$ ]] || ! git cat-file -e "$base^{commit}"; then | |
| base=$(git rev-list --max-parents=0 HEAD) | |
| fi | |
| errors=0 | |
| while IFS= read -r file; do | |
| [[ -z $file || ! -f $file ]] && continue | |
| current_lines=$(wc -l < "$file") | |
| base_lines=0 | |
| if git cat-file -e "$base:$file" 2>/dev/null; then | |
| base_lines=$(git show "$base:$file" | wc -l) | |
| fi | |
| if (( current_lines > 800 && current_lines > base_lines )); then | |
| echo "::error file=$file::$file grew from $base_lines to $current_lines lines; oversized Go files may not grow" | |
| errors=$((errors + 1)) | |
| elif (( current_lines > 800 )); then | |
| echo "::warning file=$file::$file remains oversized at $current_lines lines (baseline: $base_lines)" | |
| fi | |
| done < <(git diff --name-only --diff-filter=ACMR "$base"...HEAD -- '*.go') | |
| if (( errors > 0 )); then | |
| exit 1 | |
| fi | |
| echo "No changed oversized Go file grew" | |
| coverage: | |
| name: Test coverage | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - uses: actions/setup-go@v6 | |
| with: | |
| go-version-file: go.mod | |
| cache: true | |
| - name: Run tests with coverage and timing | |
| run: go test -v -race -count=1 -coverprofile=coverage.out -covermode=atomic -coverpkg=./internal/... -timeout 300s ./... | |
| - name: Check coverage threshold | |
| shell: bash | |
| run: | | |
| COVERAGE=$(go tool cover -func=coverage.out | tail -n1 | awk '{print $NF}' | tr -d '%') | |
| echo "Total coverage: ${COVERAGE}%" | |
| if (( $(echo "$COVERAGE < 70" | bc -l) )); then | |
| echo "::error::Coverage ${COVERAGE}% is below 70% threshold" | |
| exit 1 | |
| fi | |
| - name: Upload coverage artifact | |
| if: always() | |
| uses: actions/upload-artifact@v5 | |
| with: | |
| name: coverage | |
| path: coverage.out | |
| tidy: | |
| name: Module tidy check | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - uses: actions/setup-go@v6 | |
| with: | |
| go-version-file: go.mod | |
| cache: true | |
| - name: Check go.mod is tidy | |
| shell: bash | |
| run: | | |
| go mod tidy | |
| if [ -n "$(git status --porcelain go.mod go.sum)" ]; then | |
| echo "::error::go.mod or go.sum not tidy; run 'go mod tidy'" | |
| exit 1 | |
| fi | |
| tech-debt: | |
| name: Tech debt scan | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Scan for TODO/FIXME/HACK markers | |
| shell: bash | |
| run: | | |
| MARKERS=$(grep -rn -E '\b(TODO|FIXME|HACK|XXX|OPTIMIZE|BUG)\b' --include='*.go' . || true) | |
| if [ -n "$MARKERS" ]; then | |
| echo "== Tech debt markers found ==" | |
| echo "$MARKERS" | |
| echo "::warning::Tech debt markers found in codebase. Review and link to issues." | |
| else | |
| echo "No tech debt markers found." | |
| fi | |
| security: | |
| name: Security scan | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| permissions: | |
| contents: read | |
| security-events: write | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - uses: actions/setup-go@v6 | |
| with: | |
| go-version-file: go.mod | |
| cache: true | |
| - name: Initialize CodeQL | |
| uses: github/codeql-action/init@v3 | |
| with: | |
| languages: go | |
| - name: Build | |
| run: go build ./... | |
| - name: Perform CodeQL analysis | |
| uses: github/codeql-action/analyze@v3 | |
| integration: | |
| name: Integration tests | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - uses: actions/setup-go@v6 | |
| with: | |
| go-version-file: go.mod | |
| cache: true | |
| - name: Run integration tests | |
| run: go test -v -race -count=1 -tags=integration -timeout 300s ./... | |
| npm: | |
| name: npm launcher and package metadata | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - uses: actions/setup-node@v6 | |
| with: | |
| node-version: 22 | |
| - run: npm run test:npm | |
| - run: npm pack --dry-run |