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
9 changes: 9 additions & 0 deletions .gauntlet.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
custom_gates:
structlint:
command: ["structlint", "validate", "--format", "github"]
parser: github-annotations
line_scoped: false
dupehound:
command: ["dupehound", "scan", "--format", "github", "--quiet"]
parser: github-annotations
line_scoped: true
85 changes: 45 additions & 40 deletions .github/workflows/pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,43 +11,13 @@ permissions:
pull-requests: write

jobs:
title:
pr-title:
runs-on: ubuntu-latest
steps:
- name: Validate PR title follows Conventional Commits
env:
TITLE: ${{ github.event.pull_request.title }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
if echo "$TITLE" | grep -qE "^(feat|fix|docs|style|refactor|test|chore|build|ci|perf|revert)(\(.+\))?(!)?: .+"; then
echo "PR title is valid: $TITLE"
exit 0
fi

BODY=$(cat <<'COMMENT'
### ⚠️ Invalid PR Title

PR title must follow the **Conventional Commits** format since we use squash merge:

```
<type>[optional scope][!]: <description>
```

**Allowed types:** `feat`, `fix`, `docs`, `style`, `refactor`, `test`, `chore`, `build`, `ci`, `perf`, `revert`

**Examples:**
- `feat: add new feature`
- `fix(sandbox): resolve namespace issue`
- `feat!: breaking change`
- `chore(deps): update dependencies`
COMMENT
)

gh api "repos/${{ github.repository }}/issues/${{ github.event.pull_request.number }}/comments" \
-X POST -f body="$BODY"

echo "::error::PR title must follow Conventional Commits format"
exit 1
- uses: AxeForging/reusable-workflows/actions/pr-title-lint@291855e85ec985d1d318df82b8f9dce969b76933
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
pr-title: ${{ github.event.pull_request.title }}

review:
runs-on: ubuntu-latest
Expand All @@ -65,12 +35,47 @@ jobs:
INCREMENTAL: false
REVIEW_RULES: concise

validate:
gauntlet:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: AxeForging/structlint@main
with:
config: .structlint.yaml
comment-on-pr: "true"
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
fetch-depth: 0
- uses: actions/setup-go@v5
with:
go-version: '1.25.12'
cache: true
- name: Build candidate and install external quality gates
run: |
QUALITY_BIN="$RUNNER_TEMP/quality-bin"; mkdir -p "$QUALITY_BIN"
go build -o "$QUALITY_BIN/aigate" .
curl -fsSL https://raw.githubusercontent.com/AxeForging/structlint/main/install.sh | STRUCTLINT_VERSION=v0.6.0 STRUCTLINT_INSTALL_DIR="$QUALITY_BIN" sh
curl -fsSL https://raw.githubusercontent.com/AxeForging/dupehound/main/install.sh | DUPEHOUND_VERSION=v0.1.0 DUPEHOUND_INSTALL_DIR="$QUALITY_BIN" sh
echo "$QUALITY_BIN" >> "$GITHUB_PATH"
- uses: AxeForging/gauntlet@v0.1.0
with:
since: origin/${{ github.base_ref }}

vuln:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: '1.25.12'
cache: true
- run: go install golang.org/x/vuln/cmd/govulncheck@latest
- run: govulncheck ./...

lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: '1.25.12'
cache: true
- uses: golangci/golangci-lint-action@v9
with:
version: v2.12.2
args: --timeout=5m
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '1.24'
go-version: '1.25.12'
cache: true

- name: Run tests
Expand Down
59 changes: 0 additions & 59 deletions .github/workflows/test.yml

This file was deleted.

4 changes: 3 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
module github.com/AxeForging/aigate

go 1.25.11
go 1.25.0

toolchain go1.25.12

require (
github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2
Expand Down
12 changes: 12 additions & 0 deletions install.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#!/bin/sh
set -eu
REPO="${AIGATE_REPO:-AxeForging/aigate}"; VERSION="${AIGATE_VERSION:-latest}"; INSTALL_DIR="${AIGATE_INSTALL_DIR:-${HOME:?HOME required}/.local/bin}"
die(){ printf 'aigate installer: %s\n' "$*" >&2; exit 1; }
case "$(uname -s)" in Linux) os=linux;; Darwin) os=darwin;; *) die "unsupported OS";; esac
case "$(uname -m)" in x86_64|amd64) arch=amd64;; aarch64|arm64) arch=arm64;; *) die "unsupported architecture";; esac
if [ "$VERSION" = latest ]; then VERSION="$(curl -fsSL -o /dev/null -w '%{url_effective}' "https://github.com/$REPO/releases/latest" | awk -F/ '{print $NF}')"; fi
asset="aigate-${os}-${arch}.tar.gz"; binary="aigate-${os}-${arch}"; base="https://github.com/$REPO/releases/download/$VERSION"; tmp="$(mktemp -d)"; trap 'rm -rf "$tmp"' EXIT
curl -fsSL "$base/$asset" -o "$tmp/$asset"; curl -fsSL "$base/checksums.txt" -o "$tmp/checksums.txt"
expected="$(awk -v a="$asset" '$2==a||$2=="*"a{print $1;exit}' "$tmp/checksums.txt")"; [ -n "$expected" ] || die "checksum not found"
actual="$(sha256sum "$tmp/$asset" | awk '{print $1}')"; [ "$actual" = "$expected" ] || die "checksum verification failed"
tar -xzf "$tmp/$asset" -C "$tmp"; [ -f "$tmp/$binary" ] || die "archive missing binary"; mkdir -p "$INSTALL_DIR"; install -m 0755 "$tmp/$binary" "$INSTALL_DIR/aigate"
55 changes: 55 additions & 0 deletions integration/install_script_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package integration_test

import (
"crypto/sha256"
"fmt"
"os"
"os/exec"
"path/filepath"
"runtime"
"testing"
)

func TestInstallScript_InstallsVerifiedRelease(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("unsupported")
}
tmp := t.TempDir()
base := "aigate-" + runtime.GOOS + "-" + runtime.GOARCH
payload := filepath.Join(tmp, "payload")
mustWrite(t, filepath.Join(payload, base), "#!/bin/sh\nexit 0\n", 0o755)
asset := base + ".tar.gz"
archive := filepath.Join(tmp, asset)
if out, err := exec.Command("tar", "-czf", archive, "-C", payload, base).CombinedOutput(); err != nil {
t.Fatalf("archive: %v %s", err, out)
}
b, err := os.ReadFile(archive)
if err != nil {
t.Fatal(err)
}
sums := filepath.Join(tmp, "checksums.txt")
mustWrite(t, sums, fmt.Sprintf("%x %s\n", sha256.Sum256(b), asset), 0o644)
bin := filepath.Join(tmp, "bin")
mustWrite(t, filepath.Join(bin, "curl"), `#!/bin/sh
while [ "$#" -gt 0 ]; do case "$1" in -o) out="$2"; shift 2;; -*) shift;; *) url="$1"; shift;; esac; done
case "$url" in */checksums.txt) cp "$SUMS" "$out";; *) cp "$ARCHIVE" "$out";; esac
`, 0o755)
dest := filepath.Join(tmp, "install")
cmd := exec.Command("sh", filepath.Join("..", "install.sh"))
cmd.Env = append(os.Environ(), "PATH="+bin+":"+os.Getenv("PATH"), "AIGATE_VERSION=v0.7.0", "AIGATE_INSTALL_DIR="+dest, "ARCHIVE="+archive, "SUMS="+sums)
if out, err := cmd.CombinedOutput(); err != nil {
t.Fatalf("install: %v %s", err, out)
}
if _, err := os.Stat(filepath.Join(dest, "aigate")); err != nil {
t.Fatal(err)
}
}
func mustWrite(t *testing.T, p, s string, m os.FileMode) {
t.Helper()
if err := os.MkdirAll(filepath.Dir(p), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(p, []byte(s), m); err != nil {
t.Fatal(err)
}
}
Loading