chore: import upstream snapshot with attribution
CI / benchmark (push) Has been skipped
install-script / posix-syntax (push) Successful in 6m1s
CI / build-onnx (push) Failing after 6m43s
init-smoke / dry-run (push) Failing after 15m57s
security / govulncheck (push) Has been cancelled
security / trivy-fs (push) Has been cancelled
CI / test (1.26, ubuntu-latest) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
CI / test (1.26, macos-latest) (push) Has been cancelled
CI / build-windows (push) Has been cancelled
CI / lint (push) Has been cancelled
install-script / powershell-syntax (push) Has been cancelled
install-script / install (macos-14) (push) Has been cancelled
install-script / install (ubuntu-latest) (push) Has been cancelled
CI / benchmark (push) Has been skipped
install-script / posix-syntax (push) Successful in 6m1s
CI / build-onnx (push) Failing after 6m43s
init-smoke / dry-run (push) Failing after 15m57s
security / govulncheck (push) Has been cancelled
security / trivy-fs (push) Has been cancelled
CI / test (1.26, ubuntu-latest) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
CI / test (1.26, macos-latest) (push) Has been cancelled
CI / build-windows (push) Has been cancelled
CI / lint (push) Has been cancelled
install-script / powershell-syntax (push) Has been cancelled
install-script / install (macos-14) (push) Has been cancelled
install-script / install (ubuntu-latest) (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
name: ARM Benchmarks
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
bench-arm64:
|
||||
name: Benchmark (ARM64)
|
||||
runs-on: ubuntu-24.04-arm
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
|
||||
- uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
cache: true
|
||||
|
||||
- name: Run benchmarks
|
||||
run: |
|
||||
go test -bench=. -benchmem -count=3 -benchtime=2s -timeout=20m \
|
||||
-run='^$' \
|
||||
./internal/graph/ \
|
||||
./internal/search/ \
|
||||
./internal/parser/languages/ \
|
||||
./internal/resolver/ \
|
||||
./internal/query/ \
|
||||
./internal/indexer/ \
|
||||
./internal/analysis/ \
|
||||
| tee bench-arm64.txt
|
||||
|
||||
- name: Upload results
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
||||
with:
|
||||
name: bench-arm64-${{ github.sha }}
|
||||
path: bench-arm64.txt
|
||||
retention-days: 90
|
||||
|
||||
- name: Compare with main (PR only)
|
||||
if: github.event_name == 'pull_request'
|
||||
run: |
|
||||
go install golang.org/x/perf/cmd/benchstat@v0.0.0-20260409210113-8e83ce0f7b1c
|
||||
# Download baseline from main branch artifact if available
|
||||
echo "Benchmark results uploaded. Compare manually with benchstat."
|
||||
|
||||
bench-amd64:
|
||||
name: Benchmark (AMD64 baseline)
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
|
||||
- uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
cache: true
|
||||
|
||||
- name: Run benchmarks
|
||||
run: |
|
||||
go test -bench=. -benchmem -count=3 -benchtime=2s -timeout=20m \
|
||||
-run='^$' \
|
||||
./internal/graph/ \
|
||||
./internal/search/ \
|
||||
./internal/parser/languages/ \
|
||||
./internal/resolver/ \
|
||||
./internal/query/ \
|
||||
./internal/indexer/ \
|
||||
./internal/analysis/ \
|
||||
| tee bench-amd64.txt
|
||||
|
||||
- name: Upload results
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
||||
with:
|
||||
name: bench-amd64-${{ github.sha }}
|
||||
path: bench-amd64.txt
|
||||
retention-days: 90
|
||||
@@ -0,0 +1,119 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest]
|
||||
go-version: ['1.26']
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
|
||||
- uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
|
||||
- name: Build
|
||||
run: go build -o gortex ./cmd/gortex/
|
||||
|
||||
- name: Test
|
||||
run: go test -race -timeout=20m -coverprofile=coverage.out ./...
|
||||
|
||||
- name: Upload coverage
|
||||
if: matrix.os == 'ubuntu-latest' && matrix.go-version == '1.26'
|
||||
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v6
|
||||
with:
|
||||
files: coverage.out
|
||||
continue-on-error: true
|
||||
|
||||
build-windows:
|
||||
# CGO build smoke-test on native Windows. tree-sitter needs a C/C++
|
||||
# compiler — the GitHub windows runner ships mingw-w64 on PATH, so no
|
||||
# extra toolchain setup is required. Build-only: `go build ./...`
|
||||
# compiles every production package without pulling in the
|
||||
# platform-specific test files a full `go test` would.
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
|
||||
- uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
|
||||
- name: Build CLI
|
||||
run: go build -o gortex.exe ./cmd/gortex/
|
||||
|
||||
- name: Build all packages
|
||||
run: go build ./...
|
||||
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
|
||||
- uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
|
||||
- name: golangci-lint
|
||||
uses: golangci/golangci-lint-action@ba0d7d2ec06a0ea1cb5fa41b2e4a3ab91d21278a # v9
|
||||
with:
|
||||
version: v2.11.4
|
||||
args: --timeout=10m
|
||||
|
||||
build-onnx:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
|
||||
- uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
|
||||
- name: Install ONNX Runtime
|
||||
run: |
|
||||
wget -q https://github.com/microsoft/onnxruntime/releases/download/v1.24.4/onnxruntime-linux-x64-1.24.4.tgz
|
||||
tar xzf onnxruntime-linux-x64-1.24.4.tgz
|
||||
sudo cp onnxruntime-linux-x64-1.24.4/lib/libonnxruntime.so* /usr/local/lib/
|
||||
sudo ldconfig
|
||||
|
||||
- name: Build with ONNX tag
|
||||
run: go build -tags embeddings_onnx -o gortex-onnx ./cmd/gortex/
|
||||
|
||||
- name: Build default (Hugot bundled — no tag)
|
||||
run: go build -o gortex-default ./cmd/gortex/
|
||||
|
||||
- name: Install rust tokenizers library
|
||||
# hugot's XLA session uses the rust tokenizer, statically linked as
|
||||
# -ltokenizers, so the embeddings_gomlx XLA build needs libtokenizers.a
|
||||
# on the linker path. (The pure-Go default and the ONNX build do not.)
|
||||
run: |
|
||||
curl -fsSL https://github.com/daulet/tokenizers/releases/download/v1.27.0/libtokenizers.linux-amd64.tar.gz | tar -xz
|
||||
sudo cp libtokenizers.a /usr/lib/
|
||||
sudo cp libtokenizers.a /usr/local/lib/
|
||||
|
||||
- name: Build with GoMLX + XLA tags
|
||||
run: go build -tags "embeddings_gomlx XLA" -o gortex-gomlx ./cmd/gortex/
|
||||
|
||||
- name: Build with GoMLX tag only (compat — some docs/scripts still pass it alone)
|
||||
run: go build -tags embeddings_gomlx -o gortex-gomlx-noxla ./cmd/gortex/
|
||||
|
||||
benchmark:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name == 'pull_request'
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
|
||||
- uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
|
||||
- name: Run benchmarks
|
||||
run: go test -bench=. -benchmem -count=1 -benchtime=1s -timeout=20m ./internal/parser/languages/ ./internal/query/ ./internal/graph/
|
||||
@@ -0,0 +1,71 @@
|
||||
# Per-PR reviewer graph bundle.
|
||||
#
|
||||
# This is a TEMPLATE, not a live workflow. The `.yml.example` suffix means
|
||||
# GitHub Actions does NOT register or run it — copy it to
|
||||
# `.github/workflows/gortex-pr-review.yml` in your own repository to enable it.
|
||||
#
|
||||
# On every pull request it builds gortex, indexes the checked-out repository
|
||||
# into the daemon, runs `gortex prs bundle <N>` to produce the reviewer graph
|
||||
# bundle (changed files + graph-joined blast radius + PR-risk receipt + ranked
|
||||
# reviewers), and uploads that bundle as a CI artifact. A reviewer (or a
|
||||
# downstream agent) can then download one self-contained JSON file instead of
|
||||
# re-deriving the impact from scratch.
|
||||
#
|
||||
# The daemon self-serves PR data from the auto-provided `GITHUB_TOKEN` (mapped
|
||||
# to `GH_TOKEN`), so no extra secret configuration is needed.
|
||||
|
||||
name: Gortex PR review bundle
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: read
|
||||
|
||||
jobs:
|
||||
reviewer-bundle:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
# The daemon resolves a forge token from GH_TOKEN (then GITHUB_TOKEN).
|
||||
# GITHUB_TOKEN is injected automatically for pull_request events.
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
steps:
|
||||
- name: Check out the PR head
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
# Full history so the graph and any base-diff have real commits.
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
|
||||
- name: Build gortex
|
||||
# tree-sitter needs CGO; the ubuntu runner ships a C toolchain.
|
||||
run: go build -o gortex ./cmd/gortex/
|
||||
|
||||
- name: Start the daemon and index the repo
|
||||
run: |
|
||||
./gortex daemon start --detach
|
||||
./gortex track .
|
||||
# `index` runs synchronously and prints stats, so the graph is
|
||||
# populated and queryable before the bundle step runs.
|
||||
./gortex index .
|
||||
|
||||
- name: Build the reviewer bundle
|
||||
run: |
|
||||
./gortex prs bundle "${{ github.event.pull_request.number }}" \
|
||||
--out "pr-${{ github.event.pull_request.number }}-bundle.json"
|
||||
|
||||
- name: Upload the reviewer bundle
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: gortex-reviewer-bundle-pr-${{ github.event.pull_request.number }}
|
||||
path: pr-${{ github.event.pull_request.number }}-bundle.json
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Stop the daemon
|
||||
if: always()
|
||||
run: ./gortex daemon stop || true
|
||||
@@ -0,0 +1,120 @@
|
||||
# Smoke-test gortex init's adapter pipeline on PRs that touch it.
|
||||
# The job runs `gortex init --dry-run --json` against a synthetic
|
||||
# HOME with each agent's detection sentinel pre-created, then
|
||||
# parses the JSON report and asserts each adapter reports itself
|
||||
# configured. A schema change that breaks an adapter fails here
|
||||
# before the PR merges.
|
||||
name: init-smoke
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- "cmd/gortex/init*.go"
|
||||
- "internal/agents/**"
|
||||
- ".github/workflows/init-smoke.yml"
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- "cmd/gortex/init*.go"
|
||||
- "internal/agents/**"
|
||||
|
||||
jobs:
|
||||
dry-run:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
|
||||
- uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
cache: true
|
||||
|
||||
- name: Build gortex
|
||||
run: go build -o /tmp/gortex ./cmd/gortex
|
||||
|
||||
- name: Prepare synthetic HOME with every agent's detection sentinel
|
||||
run: |
|
||||
set -euxo pipefail
|
||||
mkdir -p "$RUNNER_TEMP/home"
|
||||
# Claude Code — no sentinel needed, always detected
|
||||
mkdir -p "$RUNNER_TEMP/home/.claude"
|
||||
# Cursor
|
||||
mkdir -p "$RUNNER_TEMP/home/.cursor"
|
||||
mkdir -p "$RUNNER_TEMP/repo/.cursor"
|
||||
# VS Code
|
||||
mkdir -p "$RUNNER_TEMP/repo/.vscode"
|
||||
# Continue
|
||||
mkdir -p "$RUNNER_TEMP/repo/.continue"
|
||||
# Kiro
|
||||
mkdir -p "$RUNNER_TEMP/repo/.kiro"
|
||||
# OpenCode
|
||||
mkdir -p "$RUNNER_TEMP/repo/.opencode"
|
||||
# Windsurf
|
||||
mkdir -p "$RUNNER_TEMP/home/.codeium"
|
||||
# Cline — per-editor globalStorage
|
||||
mkdir -p "$RUNNER_TEMP/home/.config/Code/User/globalStorage/saoudrizwan.claude-dev/settings"
|
||||
# Kilo Code
|
||||
mkdir -p "$RUNNER_TEMP/home/.config/Code/User/globalStorage/kilocode.kilo/settings"
|
||||
# Codex
|
||||
mkdir -p "$RUNNER_TEMP/home/.codex"
|
||||
# Gemini
|
||||
mkdir -p "$RUNNER_TEMP/home/.gemini"
|
||||
touch "$RUNNER_TEMP/home/.gemini/settings.json"
|
||||
# Zed (Linux path)
|
||||
mkdir -p "$RUNNER_TEMP/home/.config/zed"
|
||||
# Aider
|
||||
touch "$RUNNER_TEMP/repo/.aider.conf.yml"
|
||||
# Oh My Pi
|
||||
mkdir -p "$RUNNER_TEMP/repo/.omp"
|
||||
# OpenClaw
|
||||
mkdir -p "$RUNNER_TEMP/home/.openclaw"
|
||||
|
||||
- name: Run gortex init --dry-run --json
|
||||
id: run
|
||||
run: |
|
||||
set -euxo pipefail
|
||||
cd "$RUNNER_TEMP/repo"
|
||||
git init -q
|
||||
HOME="$RUNNER_TEMP/home" /tmp/gortex init --yes --dry-run --json > "$RUNNER_TEMP/report.json"
|
||||
cat "$RUNNER_TEMP/report.json"
|
||||
|
||||
- name: Assert every expected adapter reported detected=true
|
||||
run: |
|
||||
set -euxo pipefail
|
||||
python3 - <<'PY'
|
||||
import json, sys
|
||||
with open("${RUNNER_TEMP}/report.json".replace("${RUNNER_TEMP}", __import__("os").environ["RUNNER_TEMP"])) as f:
|
||||
data = json.load(f)
|
||||
agents = {a["name"]: a for a in data["agents"]}
|
||||
expected = [
|
||||
"claude-code", "aider", "cline", "codex", "continue",
|
||||
"cursor", "gemini", "kilocode", "kiro", "oh-my-pi", "opencode",
|
||||
"openclaw", "vscode", "windsurf", "zed",
|
||||
# "antigravity" always true when HOME is set; not a detection test.
|
||||
]
|
||||
failed = []
|
||||
for name in expected:
|
||||
a = agents.get(name)
|
||||
if not a:
|
||||
failed.append(f"{name}: missing from report")
|
||||
continue
|
||||
if not a.get("detected"):
|
||||
failed.append(f"{name}: detected=false despite sentinel")
|
||||
if failed:
|
||||
print("\n".join(failed))
|
||||
sys.exit(1)
|
||||
print(f"all {len(expected)} adapters detected")
|
||||
PY
|
||||
|
||||
- name: Assert dry-run left no files behind
|
||||
run: |
|
||||
# Dry-run must not touch disk. Allow only the sentinel
|
||||
# directories we created in the "Prepare" step.
|
||||
set -euxo pipefail
|
||||
unexpected="$(find "$RUNNER_TEMP/home" -type f 2>/dev/null | grep -v '.gemini/settings.json$' || true)"
|
||||
if [ -n "$unexpected" ]; then
|
||||
echo "ERROR: dry-run wrote files:"
|
||||
echo "$unexpected"
|
||||
exit 1
|
||||
fi
|
||||
echo "OK — dry-run produced no writes under HOME"
|
||||
@@ -0,0 +1,100 @@
|
||||
# Smoke-test scripts/install.sh on Linux + macOS against the latest published
|
||||
# release. The script is the public install path served at get.gortex.dev, so
|
||||
# any change must prove it still produces a working `gortex` binary. Coverage:
|
||||
# Linux x64 (ubuntu-latest) and macOS arm64 (macos-14). Intel macOS is not
|
||||
# tested here — GitHub retired its Intel (macos-13) runners, and install.sh is
|
||||
# arch-agnostic (only the downloaded artifact differs). Linux arm64 is
|
||||
# exercised in the release flow.
|
||||
name: install-script
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- "scripts/install.sh"
|
||||
- "scripts/install.ps1"
|
||||
- ".github/workflows/install-script.yml"
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- "scripts/install.sh"
|
||||
- "scripts/install.ps1"
|
||||
|
||||
jobs:
|
||||
posix-syntax:
|
||||
# Cheap pre-check: catch bashisms before we spin up the full matrix.
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
- name: Install shellcheck
|
||||
run: sudo apt-get update && sudo apt-get install -y shellcheck
|
||||
- name: shellcheck (sh dialect)
|
||||
run: shellcheck -s sh scripts/install.sh
|
||||
- name: POSIX dash syntax check
|
||||
run: dash -n scripts/install.sh
|
||||
|
||||
install:
|
||||
needs: posix-syntax
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-14]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
|
||||
- name: Run install.sh into an isolated prefix
|
||||
run: |
|
||||
set -eux
|
||||
export GORTEX_INSTALL_DIR="$RUNNER_TEMP/bin"
|
||||
export GORTEX_NO_PATH=1
|
||||
# Don't clobber the runner's real shell rc; HOME points to a tmp dir.
|
||||
export HOME="$RUNNER_TEMP/home"
|
||||
mkdir -p "$HOME"
|
||||
./scripts/install.sh
|
||||
|
||||
- name: Verify binary runs
|
||||
run: |
|
||||
set -eux
|
||||
"$RUNNER_TEMP/bin/gortex" version
|
||||
|
||||
- name: Re-run install (idempotency + backup)
|
||||
run: |
|
||||
set -eux
|
||||
export GORTEX_INSTALL_DIR="$RUNNER_TEMP/bin"
|
||||
export GORTEX_NO_PATH=1
|
||||
export HOME="$RUNNER_TEMP/home"
|
||||
./scripts/install.sh
|
||||
# Previous binary should have been moved aside.
|
||||
test -f "$RUNNER_TEMP/bin/gortex.previous"
|
||||
"$RUNNER_TEMP/bin/gortex" version
|
||||
|
||||
- name: PATH update writes idempotent marker block
|
||||
run: |
|
||||
set -eux
|
||||
export GORTEX_INSTALL_DIR="$RUNNER_TEMP/bin"
|
||||
export HOME="$RUNNER_TEMP/home2"
|
||||
export SHELL=/bin/bash
|
||||
mkdir -p "$HOME"
|
||||
touch "$HOME/.bashrc"
|
||||
./scripts/install.sh
|
||||
./scripts/install.sh
|
||||
# Marker block must appear exactly once.
|
||||
count=$(grep -c '^# >>> gortex installer >>>' "$HOME/.bashrc")
|
||||
test "$count" = "1"
|
||||
|
||||
powershell-syntax:
|
||||
# Parse-check install.ps1 — the PowerShell counterpart of the
|
||||
# posix-syntax job. A full install can only run once a release ships
|
||||
# Windows artifacts, so this validates that the script parses without
|
||||
# errors on every change.
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
- name: Parse install.ps1
|
||||
shell: pwsh
|
||||
run: |
|
||||
$errors = $null
|
||||
[System.Management.Automation.Language.Parser]::ParseFile(
|
||||
(Resolve-Path scripts/install.ps1), [ref]$null, [ref]$errors) | Out-Null
|
||||
if ($errors) { $errors; exit 1 }
|
||||
Write-Host "install.ps1 parses cleanly"
|
||||
@@ -0,0 +1,186 @@
|
||||
# publish-claude-plugin syncs the generated marketplace bundle from
|
||||
# this repo to gortexhq/claude-plugin on every gortex release.
|
||||
#
|
||||
# How it works:
|
||||
# 1. Checkout gortex at the released tag
|
||||
# 2. Regenerate the bundle via `make claude-plugin`
|
||||
# 3. Checkout gortexhq/claude-plugin into a sibling directory
|
||||
# 4. rsync the regenerated bundle into the publish-target checkout
|
||||
# 5. If git status is empty (bundle unchanged vs current HEAD of the
|
||||
# publish target) — exit 0, no push, no marketplace notification
|
||||
# 6. Otherwise commit + push + tag
|
||||
#
|
||||
# The "skip when unchanged" step is what gates marketplace update
|
||||
# notifications on real content changes. A gortex bug-fix release that
|
||||
# doesn't touch internal/agents/claudecode/content.go produces no diff
|
||||
# in the regenerated bundle, so the publish target is not updated and
|
||||
# marketplace users are not pinged.
|
||||
#
|
||||
# Auth: CLAUDE_PLUGIN_TOKEN is a fine-grained PAT scoped to
|
||||
# gortexhq/claude-plugin with `contents: write`. Mirrors the
|
||||
# HOMEBREW_TAP_TOKEN pattern used by .goreleaser.yml's homebrew_casks
|
||||
# block — same trust profile.
|
||||
|
||||
name: publish-claude-plugin
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
ref:
|
||||
description: "Gortex tag (or branch) to publish from. Required for manual runs."
|
||||
required: true
|
||||
type: string
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
# Serialise publishes; two concurrent runs against the same target
|
||||
# repo would race on the push.
|
||||
group: publish-claude-plugin
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
env:
|
||||
PLUGIN_REPO: gortexhq/claude-plugin
|
||||
# Branch on the publish target repo to push to. Plugin repo is
|
||||
# single-branch by design; tags drive provenance.
|
||||
PLUGIN_BRANCH: main
|
||||
|
||||
steps:
|
||||
- name: Resolve gortex ref
|
||||
id: resolve
|
||||
run: |
|
||||
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
|
||||
REF="${{ inputs.ref }}"
|
||||
else
|
||||
REF="${{ github.ref_name }}"
|
||||
fi
|
||||
if [ -z "$REF" ]; then
|
||||
echo "::error::could not resolve gortex ref to publish from"
|
||||
exit 1
|
||||
fi
|
||||
echo "ref=$REF" >> "$GITHUB_OUTPUT"
|
||||
echo "Publishing from gortex ref: $REF"
|
||||
|
||||
- name: Checkout gortex
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
ref: ${{ steps.resolve.outputs.ref }}
|
||||
path: gortex
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6
|
||||
with:
|
||||
go-version-file: gortex/go.mod
|
||||
cache: true
|
||||
cache-dependency-path: gortex/go.sum
|
||||
|
||||
- name: Regenerate plugin bundle
|
||||
working-directory: gortex
|
||||
run: make claude-plugin
|
||||
|
||||
- name: Checkout publish target
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
repository: ${{ env.PLUGIN_REPO }}
|
||||
ref: ${{ env.PLUGIN_BRANCH }}
|
||||
path: plugin-repo
|
||||
token: ${{ secrets.CLAUDE_PLUGIN_TOKEN }}
|
||||
# Need full history so the tag-already-exists check works
|
||||
# against historical tags, not just HEAD.
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Sync bundle into publish target
|
||||
run: |
|
||||
# rsync replaces the publish target's tracked content with
|
||||
# the freshly-regenerated bundle. --delete removes files that
|
||||
# no longer exist in the source (e.g. a removed skill). The
|
||||
# --exclude entries protect repo-level files we don't manage
|
||||
# from the gortex side: .git/, .github/ (publish target may
|
||||
# carry its own readme-only CI), and a top-level README/LICENSE
|
||||
# if the publish target maintains its own.
|
||||
rsync -av --delete \
|
||||
--exclude='.git/' \
|
||||
--exclude='.github/' \
|
||||
gortex/claude-plugin/ \
|
||||
plugin-repo/
|
||||
|
||||
- name: Detect content changes
|
||||
id: diff
|
||||
working-directory: plugin-repo
|
||||
run: |
|
||||
git add -A
|
||||
if git diff --cached --quiet; then
|
||||
echo "changed=false" >> "$GITHUB_OUTPUT"
|
||||
echo "No content changes vs ${{ env.PLUGIN_REPO }}@${{ env.PLUGIN_BRANCH }}; nothing to publish"
|
||||
else
|
||||
echo "changed=true" >> "$GITHUB_OUTPUT"
|
||||
echo "Detected content changes:"
|
||||
git diff --cached --stat
|
||||
fi
|
||||
|
||||
- name: Commit and push
|
||||
if: steps.diff.outputs.changed == 'true'
|
||||
working-directory: plugin-repo
|
||||
env:
|
||||
GORTEX_REF: ${{ steps.resolve.outputs.ref }}
|
||||
run: |
|
||||
git config user.name "gortex-bot"
|
||||
git config user.email "bot@gortex.dev"
|
||||
git commit -m "Sync from gortex ${GORTEX_REF}"
|
||||
# Fully-qualified refspec — fails loudly on any future name
|
||||
# collision instead of pushing the wrong ref ambiguously.
|
||||
git push origin "refs/heads/${{ env.PLUGIN_BRANCH }}:refs/heads/${{ env.PLUGIN_BRANCH }}"
|
||||
|
||||
- name: Tag publish target with gortex version
|
||||
if: steps.diff.outputs.changed == 'true'
|
||||
working-directory: plugin-repo
|
||||
env:
|
||||
GORTEX_REF: ${{ steps.resolve.outputs.ref }}
|
||||
run: |
|
||||
# Tag the publish target with the gortex release tag that
|
||||
# produced the content. Useful for provenance — a marketplace
|
||||
# user can cross-reference the plugin version against the
|
||||
# gortex release that built it.
|
||||
#
|
||||
# Three guards:
|
||||
# 1. Skip when the gortex ref isn't a release tag.
|
||||
# workflow_dispatch is allowed to take a branch (e.g.
|
||||
# "main") for testing, but tagging the publish target
|
||||
# with a branch name would collide with its own branches
|
||||
# of the same name and fail the push with "src refspec
|
||||
# matches more than one".
|
||||
# 2. Skip silently if the tag already exists in the publish
|
||||
# target — re-runs against the same gortex tag that
|
||||
# produced no content change should be no-ops, not
|
||||
# half-pushed states.
|
||||
# 3. Use fully-qualified refspecs on the push so a future
|
||||
# collision (branch and tag with the same name in the
|
||||
# publish target) fails loudly rather than ambiguously.
|
||||
if [[ ! "${GORTEX_REF}" =~ ^v[0-9] ]]; then
|
||||
echo "Ref '${GORTEX_REF}' is not a release tag (does not match ^v[0-9]); skipping publish-target tag"
|
||||
exit 0
|
||||
fi
|
||||
if git rev-parse --verify "refs/tags/${GORTEX_REF}" >/dev/null 2>&1; then
|
||||
echo "Tag ${GORTEX_REF} already exists in ${{ env.PLUGIN_REPO }}; skipping"
|
||||
exit 0
|
||||
fi
|
||||
git tag -a "${GORTEX_REF}" -m "Plugin bundle from gortex ${GORTEX_REF}"
|
||||
git push origin "refs/tags/${GORTEX_REF}:refs/tags/${GORTEX_REF}"
|
||||
|
||||
- name: Summary
|
||||
if: always()
|
||||
run: |
|
||||
{
|
||||
echo "### Publish summary"
|
||||
echo ""
|
||||
echo "- Source ref: \`${{ steps.resolve.outputs.ref }}\`"
|
||||
echo "- Target repo: \`${{ env.PLUGIN_REPO }}\`"
|
||||
echo "- Content changed: \`${{ steps.diff.outputs.changed }}\`"
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
@@ -0,0 +1,672 @@
|
||||
name: Release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
# id-token: write is required for:
|
||||
# 1. cosign keyless signing via GitHub's OIDC token
|
||||
# 2. SLSA-3 provenance generation (the reusable workflow below also sets it)
|
||||
id-token: write
|
||||
|
||||
jobs:
|
||||
# darwin is built on a NATIVE macOS runner so Apple's ld sets the
|
||||
# SG_READ_ONLY flag on the __DATA_CONST segment. The cross-compiled cask
|
||||
# shipped without it and aborted under the macOS 15+/Tahoe dyld
|
||||
# ("__DATA_CONST segment missing SG_READ_ONLY flag", issue #176). This job
|
||||
# links arm64 natively + amd64 via `clang -arch x86_64`, codesigns each
|
||||
# Mach-O, smoke-tests the flag (and actually runs the arm64 binary on this
|
||||
# Sequoia runner — the exact `gortex --version` repro), packages the tar.gz
|
||||
# archives in goreleaser's layout, and uploads them for the `release` job to
|
||||
# merge, notarize, sign, and reference from the homebrew cask.
|
||||
build-darwin:
|
||||
runs-on: macos-15
|
||||
# Only reads the repo and hands off an artifact — no release writes here.
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
|
||||
- uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
|
||||
- name: Build darwin binaries (native Apple toolchain)
|
||||
run: |
|
||||
set -euo pipefail
|
||||
TAG="${GITHUB_REF#refs/tags/}"
|
||||
# Mirror goreleaser's ldflags so the darwin binaries report the same
|
||||
# version/commit as the linux ones. {{ .Version }} is the tag without
|
||||
# its leading "v".
|
||||
VERSION="${TAG#v}"
|
||||
COMMIT="$(git rev-parse --short HEAD)"
|
||||
DATE="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
||||
LDFLAGS="-s -w -X main.version=${VERSION} -X main.commit=${COMMIT} -X main.date=${DATE}"
|
||||
mkdir -p dist-darwin
|
||||
|
||||
# arm64: native link on this Apple-Silicon runner.
|
||||
CGO_ENABLED=1 GOOS=darwin GOARCH=arm64 \
|
||||
go build -ldflags "$LDFLAGS" -o dist-darwin/gortex_darwin_arm64 ./cmd/gortex/
|
||||
|
||||
# amd64: cross within Apple's clang (the macOS SDK is universal), so
|
||||
# Apple's ld still sets SG_READ_ONLY. CC + CXX both target x86_64
|
||||
# because some tree-sitter scanners are C++.
|
||||
CGO_ENABLED=1 GOOS=darwin GOARCH=amd64 \
|
||||
CC="clang -arch x86_64" CXX="clang++ -arch x86_64" \
|
||||
go build -ldflags "$LDFLAGS" -o dist-darwin/gortex_darwin_amd64 ./cmd/gortex/
|
||||
|
||||
- name: Stage macOS signing material
|
||||
env:
|
||||
P12_B64: ${{ secrets.MACOS_CERTIFICATE_P12 }}
|
||||
P12_PASS: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
SIGNING_DIR=/tmp/macos-signing
|
||||
mkdir -p "$SIGNING_DIR"; chmod 700 "$SIGNING_DIR"
|
||||
echo "$P12_B64" | base64 -d > "$SIGNING_DIR/cert.p12"
|
||||
printf '%s' "$P12_PASS" > "$SIGNING_DIR/cert.pass"
|
||||
chmod 600 "$SIGNING_DIR"/cert.*
|
||||
|
||||
# Reuse the exact signing path (scripts/sign-macos-build-hook.sh)
|
||||
# via rcodesign — just the native aarch64-apple-darwin build of the
|
||||
# tool instead of the linux-musl one. Pin version + sha256 together;
|
||||
# the apple-codesign release page publishes both.
|
||||
RCODESIGN_VERSION=0.29.0
|
||||
RCODESIGN_SHA256=d1a532150adaf90048260d76359261aa716abafc45c53c5dc18845029184334a
|
||||
RCODESIGN_TARBALL="apple-codesign-${RCODESIGN_VERSION}-aarch64-apple-darwin.tar.gz"
|
||||
curl -fsSL \
|
||||
"https://github.com/indygreg/apple-platform-rs/releases/download/apple-codesign%2F${RCODESIGN_VERSION}/${RCODESIGN_TARBALL}" \
|
||||
-o "$SIGNING_DIR/$RCODESIGN_TARBALL"
|
||||
echo "$RCODESIGN_SHA256 $SIGNING_DIR/$RCODESIGN_TARBALL" | shasum -a 256 -c -
|
||||
tar -xzf "$SIGNING_DIR/$RCODESIGN_TARBALL" -C "$SIGNING_DIR" --strip-components=1
|
||||
chmod +x "$SIGNING_DIR/rcodesign"
|
||||
rm "$SIGNING_DIR/$RCODESIGN_TARBALL"
|
||||
|
||||
- name: Codesign darwin binaries
|
||||
run: |
|
||||
set -euo pipefail
|
||||
sh scripts/sign-macos-build-hook.sh darwin dist-darwin/gortex_darwin_arm64
|
||||
sh scripts/sign-macos-build-hook.sh darwin dist-darwin/gortex_darwin_amd64
|
||||
|
||||
- name: Smoke-test __DATA_CONST SG_READ_ONLY flag (issue #176)
|
||||
run: |
|
||||
set -euo pipefail
|
||||
sh scripts/verify-macho-readonly.sh dist-darwin/gortex_darwin_arm64
|
||||
sh scripts/verify-macho-readonly.sh dist-darwin/gortex_darwin_amd64
|
||||
# End-to-end repro of #176: actually load + run the binary on this
|
||||
# Sequoia runner, whose dyld enforces the flag. A missing flag aborts
|
||||
# here exactly like `gortex --version` did for users on Tahoe.
|
||||
chmod +x dist-darwin/gortex_darwin_arm64
|
||||
./dist-darwin/gortex_darwin_arm64 version
|
||||
# amd64 runs under Rosetta when present; otherwise the static otool
|
||||
# check above already covers it.
|
||||
if arch -x86_64 /usr/bin/true >/dev/null 2>&1; then
|
||||
chmod +x dist-darwin/gortex_darwin_amd64
|
||||
arch -x86_64 ./dist-darwin/gortex_darwin_amd64 version
|
||||
else
|
||||
echo "rosetta unavailable; amd64 covered by the otool flag check"
|
||||
fi
|
||||
|
||||
- name: Package darwin archives
|
||||
run: |
|
||||
set -euo pipefail
|
||||
# Match goreleaser's tar.gz layout: the binary as `gortex` at the
|
||||
# archive root, alongside LICENSE/README (goreleaser's default
|
||||
# archive files). install.sh + the cask expect this exact name:
|
||||
# gortex_<os>_<arch>.tar.gz.
|
||||
mkdir -p dist
|
||||
for arch in arm64 amd64; do
|
||||
stage="$(mktemp -d)"
|
||||
cp "dist-darwin/gortex_darwin_${arch}" "$stage/gortex"
|
||||
chmod +x "$stage/gortex"
|
||||
cp LICENSE.md README.md "$stage/"
|
||||
tar -C "$stage" -czf "dist/gortex_darwin_${arch}.tar.gz" gortex LICENSE.md README.md
|
||||
rm -rf "$stage"
|
||||
done
|
||||
ls -la dist/
|
||||
|
||||
- name: Wipe macOS signing material
|
||||
if: always()
|
||||
run: rm -rf /tmp/macos-signing
|
||||
|
||||
- name: Upload darwin archives
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: darwin-archives
|
||||
path: dist/gortex_darwin_*.tar.gz
|
||||
if-no-files-found: error
|
||||
retention-days: 1
|
||||
|
||||
release:
|
||||
needs: build-darwin
|
||||
runs-on: ubuntu-latest
|
||||
# Expose sha256 hashes of every release artifact so the provenance job
|
||||
# can feed them to the SLSA generator.
|
||||
outputs:
|
||||
hashes: ${{ steps.hash.outputs.hashes }}
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
# goreleaser reads the tag list to build the changelog. Without
|
||||
# full history the changelog section will be empty or wrong.
|
||||
fetch-depth: 0
|
||||
|
||||
# cosign is installed on the host (outside the goreleaser-cross
|
||||
# container) so keyless OIDC signing can use the runner's identity
|
||||
# token directly — no need to plumb ACTIONS_ID_TOKEN_REQUEST_* vars
|
||||
# through `docker run`.
|
||||
- uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2
|
||||
with:
|
||||
cosign-release: v2.4.1
|
||||
|
||||
# Stage the Apple notary credentials + rcodesign (linux-musl build)
|
||||
# under one 0700 directory for the post-archive "Notarize macOS
|
||||
# binaries" step. The darwin binaries were already codesigned in the
|
||||
# build-darwin job, so the signing cert is NOT needed here — only the
|
||||
# notary API key. The dir is wiped unconditionally at the end of the
|
||||
# job (see "Wipe macOS signing material").
|
||||
- name: Stage macOS notary material
|
||||
env:
|
||||
NOTARY_B64: ${{ secrets.MACOS_NOTARY_API_KEY }}
|
||||
KEY_ID: ${{ secrets.MACOS_NOTARY_KEY_ID }}
|
||||
ISSUER_ID: ${{ secrets.MACOS_NOTARY_ISSUER_ID }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
SIGNING_DIR=/tmp/macos-signing
|
||||
mkdir -p "$SIGNING_DIR"
|
||||
chmod 700 "$SIGNING_DIR"
|
||||
|
||||
echo "$NOTARY_B64" | base64 -d > "$SIGNING_DIR/notary.p8"
|
||||
|
||||
# Pin rcodesign to a known-good release. apple-codesign tags use
|
||||
# `apple-codesign/X.Y.Z`; the slash is URL-escaped as %2F. Bump
|
||||
# version + sha256 together — the release page publishes both.
|
||||
RCODESIGN_VERSION=0.29.0
|
||||
RCODESIGN_SHA256=dbe85cedd8ee4217b64e9a0e4c2aef92ab8bcaaa41f20bde99781ff02e600002
|
||||
RCODESIGN_TARBALL="apple-codesign-${RCODESIGN_VERSION}-x86_64-unknown-linux-musl.tar.gz"
|
||||
curl -fsSL \
|
||||
"https://github.com/indygreg/apple-platform-rs/releases/download/apple-codesign%2F${RCODESIGN_VERSION}/${RCODESIGN_TARBALL}" \
|
||||
-o "$SIGNING_DIR/$RCODESIGN_TARBALL"
|
||||
echo "$RCODESIGN_SHA256 $SIGNING_DIR/$RCODESIGN_TARBALL" | sha256sum -c -
|
||||
tar -xzf "$SIGNING_DIR/$RCODESIGN_TARBALL" -C "$SIGNING_DIR" --strip-components=1
|
||||
chmod +x "$SIGNING_DIR/rcodesign"
|
||||
rm "$SIGNING_DIR/$RCODESIGN_TARBALL"
|
||||
|
||||
# rcodesign notary-submit wants a single JSON file with the
|
||||
# issuer/key id and the .p8 contents pre-encoded — done once
|
||||
# here so the post-archive step is just an upload.
|
||||
"$SIGNING_DIR/rcodesign" encode-app-store-connect-api-key \
|
||||
-o "$SIGNING_DIR/notary.json" \
|
||||
"$ISSUER_ID" "$KEY_ID" "$SIGNING_DIR/notary.p8"
|
||||
|
||||
chmod 600 "$SIGNING_DIR"/notary.*
|
||||
|
||||
# Pull the darwin tar.gz archives the build-darwin job produced (signed,
|
||||
# flag-correct, native-linked) into dist-darwin/. They are merged into
|
||||
# dist/ AFTER goreleaser runs — goreleaser's `--clean` wipes dist/, so
|
||||
# staging them there first would lose them.
|
||||
- name: Fetch darwin archives
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
name: darwin-archives
|
||||
path: dist-darwin
|
||||
|
||||
- name: Run GoReleaser (linux build via Docker)
|
||||
# goreleaser-cross ships the aarch64/x86_64 linux gcc toolchains so the
|
||||
# two linux CGO targets cross-compile from one ubuntu runner. darwin is
|
||||
# built separately (build-darwin job) and windows separately
|
||||
# (release-windows job); goreleaser here owns the linux archives, the
|
||||
# deb/rpm/apk packages, checksums.txt, and the GitHub release. The
|
||||
# homebrew cask is assembled in the "Publish homebrew cask" step below,
|
||||
# once every platform's tarball hash exists.
|
||||
#
|
||||
# Image tag is pinned to the Go major.minor; bump together with
|
||||
# go.mod and the setup-go step elsewhere in the workflows.
|
||||
run: |
|
||||
docker run --rm --privileged \
|
||||
-v "$PWD":/go/src/gortex \
|
||||
-w /go/src/gortex \
|
||||
-e GITHUB_TOKEN \
|
||||
ghcr.io/goreleaser/goreleaser-cross:v1.26 \
|
||||
release --clean
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
# goreleaser-cross runs as root inside the container, so everything
|
||||
# in dist/ is owned by root:root on the host. The subsequent cosign
|
||||
# and SLSA steps run as the non-root `runner` user and need to write
|
||||
# .sig / .pem files next to the artifacts — reclaim ownership now
|
||||
# before any permission-denied errors surface.
|
||||
- name: Reclaim ownership of dist/ from Docker
|
||||
run: sudo chown -R "$(id -u):$(id -g)" dist
|
||||
|
||||
# Now that goreleaser's --clean has run, fold the darwin tarballs in
|
||||
# alongside the linux ones so the notarize / cosign / checksum / upload
|
||||
# steps below treat all platforms uniformly.
|
||||
- name: Merge darwin archives into dist/
|
||||
run: |
|
||||
set -euo pipefail
|
||||
cp dist-darwin/gortex_darwin_*.tar.gz dist/
|
||||
ls -la dist/
|
||||
|
||||
# The build-darwin job already codesigned each darwin Mach-O. Notarization
|
||||
# is Apple's blessing on that signature — they don't see the binary
|
||||
# again, just hash + signature metadata, so the tarball on disk is
|
||||
# left untouched. cosign + the checksums + SLSA provenance below all
|
||||
# cover those same untouched bytes.
|
||||
#
|
||||
# Bare Mach-O can't be stapled (only .app/.pkg/.dmg can), so we
|
||||
# don't pass --staple. Online macs fetch the ticket from Apple on
|
||||
# first run; offline-first UX would require a signed .pkg.
|
||||
- name: Notarize macOS binaries
|
||||
run: |
|
||||
set -euo pipefail
|
||||
SIGNING_DIR=/tmp/macos-signing
|
||||
shopt -s nullglob
|
||||
for tgz in dist/gortex_darwin_*.tar.gz; do
|
||||
workdir="$(mktemp -d)"
|
||||
tar -xzf "$tgz" -C "$workdir"
|
||||
(cd "$workdir" && zip -q notary.zip gortex)
|
||||
"$SIGNING_DIR/rcodesign" notary-submit \
|
||||
--api-key-file "$SIGNING_DIR/notary.json" \
|
||||
--wait \
|
||||
"$workdir/notary.zip"
|
||||
rm -rf "$workdir"
|
||||
done
|
||||
|
||||
# goreleaser's checksums.txt only hashed its own (linux + nfpm)
|
||||
# artifacts. Append the darwin tarballs so install.sh — which verifies
|
||||
# every download against checksums.txt — and the cask both resolve. Done
|
||||
# before cosign so the signature covers the final checksums.txt. (The
|
||||
# release-windows job appends the windows zip the same way.)
|
||||
- name: Append darwin checksums
|
||||
run: |
|
||||
set -euo pipefail
|
||||
cd dist
|
||||
for f in gortex_darwin_*.tar.gz; do
|
||||
grep -q " ${f}$" checksums.txt || sha256sum "$f" >> checksums.txt
|
||||
done
|
||||
sort -o checksums.txt checksums.txt
|
||||
cat checksums.txt
|
||||
|
||||
# Keyless cosign signing: each artifact gets a `.sig` (signature) and
|
||||
# `.pem` (certificate chain binding the signature to this workflow's
|
||||
# GitHub OIDC identity). Consumers verify with:
|
||||
#
|
||||
# cosign verify-blob \
|
||||
# --certificate gortex_linux_amd64.tar.gz.pem \
|
||||
# --signature gortex_linux_amd64.tar.gz.sig \
|
||||
# --certificate-identity-regexp 'https://github\.com/zzet/gortex/.*' \
|
||||
# --certificate-oidc-issuer https://token.actions.githubusercontent.com \
|
||||
# gortex_linux_amd64.tar.gz
|
||||
- name: Sign release artifacts with cosign
|
||||
env:
|
||||
COSIGN_YES: "true"
|
||||
run: |
|
||||
cd dist
|
||||
shopt -s nullglob
|
||||
for f in *.tar.gz *.zip *.deb *.rpm *.apk checksums.txt; do
|
||||
echo "Signing $f..."
|
||||
cosign sign-blob \
|
||||
--output-signature "${f}.sig" \
|
||||
--output-certificate "${f}.pem" \
|
||||
"$f"
|
||||
done
|
||||
ls -la *.sig *.pem
|
||||
|
||||
# goreleaser created the release with the linux artifacts + a linux-only
|
||||
# checksums.txt. Add the darwin tarballs and overwrite checksums.txt with
|
||||
# the darwin-inclusive version (--clobber). The darwin .sig/.pem ride
|
||||
# along in the "Upload signatures" step below.
|
||||
- name: Upload darwin archives + refreshed checksums to release
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
TAG="${GITHUB_REF#refs/tags/}"
|
||||
gh release upload "$TAG" \
|
||||
dist/gortex_darwin_*.tar.gz \
|
||||
dist/checksums.txt \
|
||||
--clobber
|
||||
|
||||
# Goreleaser already created the release and uploaded the primary
|
||||
# artifacts. Append .sig + .pem files to the same release so
|
||||
# verification instructions in README point at a single URL set.
|
||||
- name: Upload signatures to release
|
||||
uses: softprops/action-gh-release@718ea10b132b3b2eba29c1007bb80653f286566b # v3
|
||||
with:
|
||||
files: |
|
||||
dist/*.sig
|
||||
dist/*.pem
|
||||
# softprops/action-gh-release@718ea10b132b3b2eba29c1007bb80653f286566b # v3 appends to an existing release
|
||||
# when the tag already exists (it does — goreleaser made it a
|
||||
# moment ago). Never fail_on_unmatched_files here — a failed
|
||||
# signing step should surface as a signing error, not as a
|
||||
# missing-files upload error.
|
||||
fail_on_unmatched_files: false
|
||||
|
||||
- name: Compute artifact hashes for SLSA provenance
|
||||
id: hash
|
||||
run: |
|
||||
cd dist
|
||||
shopt -s nullglob
|
||||
# SLSA reusable generator wants base64-encoded sha256sum output
|
||||
# (one "hash filename" line per artifact). We hash the primary
|
||||
# artifacts only — .sig/.pem are attestations of these files,
|
||||
# they don't need their own provenance.
|
||||
HASHES=$(sha256sum *.tar.gz *.zip *.deb *.rpm *.apk | base64 -w0)
|
||||
echo "hashes=$HASHES" >> "$GITHUB_OUTPUT"
|
||||
|
||||
# Assemble the homebrew cask and push it to zzet/homebrew-tap. goreleaser
|
||||
# used to generate this, but the cask references all four os/arch tarballs
|
||||
# and darwin is no longer a goreleaser artifact, so we build it here from
|
||||
# the now-complete checksums.txt. Completions are still generated on the
|
||||
# user's machine at install time (generate_completions_from_executable),
|
||||
# so nothing needs to run at release time. HOMEBREW_TAP_TOKEN is a PAT
|
||||
# with `repo` scope on the tap; GITHUB_TOKEN can only push to this repo.
|
||||
- name: Publish homebrew cask
|
||||
env:
|
||||
HOMEBREW_TAP_TOKEN: ${{ secrets.HOMEBREW_TAP_TOKEN }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
TAG="${GITHUB_REF#refs/tags/}"
|
||||
VERSION="${TAG#v}"
|
||||
|
||||
# Pull each tarball's sha256 out of the darwin-inclusive checksums.txt.
|
||||
sha() { awk -v f="$1" '$2==f || $2=="*"f {print $1; exit}' dist/checksums.txt; }
|
||||
DA_AMD64="$(sha gortex_darwin_amd64.tar.gz)"
|
||||
DA_ARM64="$(sha gortex_darwin_arm64.tar.gz)"
|
||||
LX_AMD64="$(sha gortex_linux_amd64.tar.gz)"
|
||||
LX_ARM64="$(sha gortex_linux_arm64.tar.gz)"
|
||||
for pair in "darwin_amd64:$DA_AMD64" "darwin_arm64:$DA_ARM64" \
|
||||
"linux_amd64:$LX_AMD64" "linux_arm64:$LX_ARM64"; do
|
||||
[ -n "${pair#*:}" ] || { echo "FATAL: no sha256 for gortex_${pair%%:*}.tar.gz in checksums.txt"; exit 1; }
|
||||
done
|
||||
|
||||
# Generated cask. #{version} is Ruby interpolation evaluated by brew —
|
||||
# it must stay literal, so it is NOT shell-expanded here (no leading $).
|
||||
cat > gortex.rb <<EOF
|
||||
# This file is generated by release.yml. DO NOT EDIT.
|
||||
cask "gortex" do
|
||||
version "${VERSION}"
|
||||
|
||||
on_macos do
|
||||
on_intel do
|
||||
sha256 "${DA_AMD64}"
|
||||
url "https://github.com/zzet/gortex/releases/download/v#{version}/gortex_darwin_amd64.tar.gz"
|
||||
end
|
||||
on_arm do
|
||||
sha256 "${DA_ARM64}"
|
||||
url "https://github.com/zzet/gortex/releases/download/v#{version}/gortex_darwin_arm64.tar.gz"
|
||||
end
|
||||
end
|
||||
|
||||
on_linux do
|
||||
on_intel do
|
||||
sha256 "${LX_AMD64}"
|
||||
url "https://github.com/zzet/gortex/releases/download/v#{version}/gortex_linux_amd64.tar.gz"
|
||||
end
|
||||
on_arm do
|
||||
sha256 "${LX_ARM64}"
|
||||
url "https://github.com/zzet/gortex/releases/download/v#{version}/gortex_linux_arm64.tar.gz"
|
||||
end
|
||||
end
|
||||
|
||||
name "gortex"
|
||||
desc "Code intelligence engine that indexes repositories into an in-memory knowledge graph."
|
||||
homepage "https://github.com/zzet/gortex"
|
||||
|
||||
livecheck do
|
||||
skip "Auto-generated on release."
|
||||
end
|
||||
|
||||
binary "gortex"
|
||||
|
||||
generate_completions_from_executable "gortex",
|
||||
shell_parameter_format: :cobra
|
||||
|
||||
# No zap stanza required
|
||||
end
|
||||
EOF
|
||||
|
||||
git clone "https://x-access-token:${HOMEBREW_TAP_TOKEN}@github.com/zzet/homebrew-tap.git" tap
|
||||
mkdir -p tap/Casks
|
||||
cp gortex.rb tap/Casks/gortex.rb
|
||||
cd tap
|
||||
git add Casks/gortex.rb
|
||||
if git diff --cached --quiet; then
|
||||
echo "cask already current for ${VERSION}"
|
||||
else
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||
git commit -m "gortex: ${VERSION}"
|
||||
git push
|
||||
echo "published cask ${VERSION}"
|
||||
fi
|
||||
|
||||
# Always wipe signing material — even on failure — so a P12 / .p8
|
||||
# never lingers on a runner image cache or in actions/upload-artifact
|
||||
# debug bundles. shred clears the inode contents before unlink.
|
||||
- name: Wipe macOS signing material
|
||||
if: always()
|
||||
run: |
|
||||
if [ -d /tmp/macos-signing ]; then
|
||||
find /tmp/macos-signing -type f -exec shred -uf {} \; 2>/dev/null || true
|
||||
rm -rf /tmp/macos-signing
|
||||
fi
|
||||
|
||||
# Windows is built on a NATIVE windows runner: the CGo tree-sitter
|
||||
# bindings need a real C/C++ toolchain (mingw-w64 ships on PATH there),
|
||||
# and goreleaser-cross targets unix only. This job builds a statically
|
||||
# linked, self-contained .exe (no runtime DLLs to ship), zips it,
|
||||
# cosign-signs, and appends the zip to the release the `release` job
|
||||
# already created.
|
||||
release-windows:
|
||||
needs: release
|
||||
runs-on: windows-latest
|
||||
permissions:
|
||||
contents: write
|
||||
id-token: write
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
|
||||
- uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
|
||||
- uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2
|
||||
with:
|
||||
cosign-release: v2.4.1
|
||||
|
||||
- name: Build gortex.exe (static mingw runtime)
|
||||
shell: bash
|
||||
env:
|
||||
CGO_ENABLED: "1"
|
||||
run: |
|
||||
set -euo pipefail
|
||||
VER="${GITHUB_REF#refs/tags/}"
|
||||
# -extldflags=-static folds the mingw C/C++ runtime (libstdc++,
|
||||
# libgcc, libwinpthread) into the .exe so it ships as a single
|
||||
# self-contained binary — nothing to bundle alongside. The C++
|
||||
# stdlib is in the link at all because some tree-sitter grammars
|
||||
# carry C++ external scanners (e.g. go-sitter-forest norg); static
|
||||
# linking just puts it inside the .exe instead of a DLL.
|
||||
go build \
|
||||
-ldflags "-s -w -X main.version=${VER} -X main.commit=$(git rev-parse --short HEAD) -X main.date=$(date -u +%Y-%m-%dT%H:%M:%SZ) -extldflags=-static" \
|
||||
-o gortex.exe ./cmd/gortex/
|
||||
|
||||
- name: Verify gortex.exe is self-contained
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
# The static link must leave no dependency on a mingw runtime DLL;
|
||||
# a partially static .exe would fail to start where that DLL is
|
||||
# absent. If objdump is available, fail the release on any leaked
|
||||
# mingw runtime import.
|
||||
objdump=""
|
||||
for cand in objdump x86_64-w64-mingw32-objdump; do
|
||||
command -v "$cand" >/dev/null 2>&1 && { objdump="$cand"; break; }
|
||||
done
|
||||
if [ -n "$objdump" ]; then
|
||||
echo "imported DLLs:"; "$objdump" -p gortex.exe | grep -i 'DLL Name' || true
|
||||
if "$objdump" -p gortex.exe | grep -iqE 'libstdc\+\+|libgcc_s|libwinpthread'; then
|
||||
echo "FATAL: gortex.exe still imports a mingw runtime DLL — static link incomplete"
|
||||
exit 1
|
||||
fi
|
||||
echo "ok: no mingw runtime DLL imports"
|
||||
else
|
||||
echo "WARN: objdump not found; skipping self-containment check"
|
||||
fi
|
||||
|
||||
- name: Zip (gortex_windows_amd64.zip)
|
||||
shell: pwsh
|
||||
run: Compress-Archive -Path gortex.exe -DestinationPath gortex_windows_amd64.zip -Force
|
||||
|
||||
- name: Sign + upload to release
|
||||
shell: bash
|
||||
env:
|
||||
COSIGN_YES: "true"
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
cosign sign-blob \
|
||||
--output-signature gortex_windows_amd64.zip.sig \
|
||||
--output-certificate gortex_windows_amd64.zip.pem \
|
||||
gortex_windows_amd64.zip
|
||||
TAG="${GITHUB_REF#refs/tags/}"
|
||||
gh release upload "$TAG" \
|
||||
gortex_windows_amd64.zip \
|
||||
gortex_windows_amd64.zip.sig \
|
||||
gortex_windows_amd64.zip.pem \
|
||||
--clobber
|
||||
|
||||
# Append the windows zip's sha256 to the release checksums.txt so
|
||||
# the one-line installer (scripts/install.ps1, which verifies
|
||||
# against checksums.txt) covers windows too — the unix goreleaser
|
||||
# run only hashed its own artifacts. needs:release guarantees
|
||||
# checksums.txt already exists.
|
||||
sha="$(sha256sum gortex_windows_amd64.zip | awk '{print $1}')"
|
||||
gh release download "$TAG" --pattern checksums.txt --clobber 2>/dev/null || : > checksums.txt
|
||||
if ! grep -q "gortex_windows_amd64.zip" checksums.txt; then
|
||||
printf '%s gortex_windows_amd64.zip\n' "$sha" >> checksums.txt
|
||||
gh release upload "$TAG" checksums.txt --clobber
|
||||
fi
|
||||
|
||||
- name: Publish Scoop manifest
|
||||
# Push a refreshed `gortex` manifest to gortexhq/scoop-bucket so
|
||||
# `scoop install gortex` resolves this release. SCOOP_BUCKET_TOKEN is
|
||||
# a PAT with `repo` scope on that bucket (GITHUB_TOKEN can only push
|
||||
# to the source repo). Non-blocking + self-skipping: a bucket hiccup
|
||||
# must not fail a release whose binary already shipped, and a
|
||||
# token-less fork just skips it.
|
||||
continue-on-error: true
|
||||
shell: bash
|
||||
env:
|
||||
SCOOP_BUCKET_TOKEN: ${{ secrets.SCOOP_BUCKET_TOKEN }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [ -z "${SCOOP_BUCKET_TOKEN:-}" ]; then
|
||||
echo "SCOOP_BUCKET_TOKEN not set; skipping scoop manifest publish"
|
||||
exit 0
|
||||
fi
|
||||
TAG="${GITHUB_REF#refs/tags/}"
|
||||
VER="${TAG#v}"
|
||||
URL="https://github.com/${GITHUB_REPOSITORY}/releases/download/${TAG}/gortex_windows_amd64.zip"
|
||||
SHA="$(sha256sum gortex_windows_amd64.zip | awk '{print $1}')"
|
||||
|
||||
# Build the manifest with jq so escaping + validity are guaranteed.
|
||||
# `bin` shims gortex.exe; checkver/autoupdate let scoop's tooling
|
||||
# track future releases (the $version token is literal on purpose).
|
||||
jq -n \
|
||||
--arg version "$VER" \
|
||||
--arg url "$URL" \
|
||||
--arg hash "$SHA" \
|
||||
--arg homepage "https://github.com/${GITHUB_REPOSITORY}" \
|
||||
--arg autourl "https://github.com/${GITHUB_REPOSITORY}/releases/download/v\$version/gortex_windows_amd64.zip" \
|
||||
'{
|
||||
version: $version,
|
||||
description: "Code intelligence engine that indexes repositories into an in-memory knowledge graph.",
|
||||
homepage: $homepage,
|
||||
license: "Apache-2.0",
|
||||
architecture: { "64bit": { url: $url, hash: $hash } },
|
||||
bin: "gortex.exe",
|
||||
checkver: "github",
|
||||
autoupdate: { architecture: { "64bit": { url: $autourl } } }
|
||||
}' > gortex.json
|
||||
|
||||
# Token in the clone URL — GitHub Actions masks the secret in logs.
|
||||
git clone "https://x-access-token:${SCOOP_BUCKET_TOKEN}@github.com/gortexhq/scoop-bucket.git" scoop-bucket
|
||||
cd scoop-bucket
|
||||
# Honour the bucket's layout: scoop reads manifests from the repo
|
||||
# root or a bucket/ subdir. Update in place if one exists, else use
|
||||
# the conventional bucket/ subdir.
|
||||
if [ -f gortex.json ]; then dest="gortex.json"; else mkdir -p bucket; dest="bucket/gortex.json"; fi
|
||||
cp ../gortex.json "$dest"
|
||||
git add "$dest"
|
||||
if git diff --cached --quiet; then
|
||||
echo "scoop manifest already current for ${VER}"
|
||||
else
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||
git commit -m "gortex: ${VER}"
|
||||
git push
|
||||
echo "published scoop manifest ${VER} -> $dest"
|
||||
fi
|
||||
|
||||
# SLSA-3 provenance via the OpenSSF reusable workflow. This runs in a
|
||||
# separate, isolated job that the `release` job can't tamper with —
|
||||
# that isolation is what elevates us from SLSA-2 to SLSA-3. Output is
|
||||
# a `multiple.intoto.jsonl` file attached to the release that
|
||||
# consumers verify with https://github.com/slsa-framework/slsa-verifier.
|
||||
provenance:
|
||||
needs: release
|
||||
permissions:
|
||||
actions: read
|
||||
id-token: write
|
||||
contents: write
|
||||
# Pinned by tag, not SHA: the SLSA generator verifies its builder
|
||||
# binary against the tag name in its workflow ref, so SHA pinning
|
||||
# breaks the integrity check (exits 27 with SUCCESS=false). Scorecard
|
||||
# exempts slsa-framework reusable workflows from the SHA-pin rule.
|
||||
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v2.1.0
|
||||
with:
|
||||
base64-subjects: ${{ needs.release.outputs.hashes }}
|
||||
upload-assets: true
|
||||
|
||||
# VirusTotal scan each primary artifact against ~72 AV engines and
|
||||
# append a results badge to the release notes. Needs a VT_API_KEY
|
||||
# repo secret (free tier, 500 requests/day). Non-blocking — VT
|
||||
# outages shouldn't fail a release.
|
||||
virustotal:
|
||||
needs: release
|
||||
runs-on: ubuntu-latest
|
||||
continue-on-error: true
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
|
||||
- name: Download release assets
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
mkdir -p dist
|
||||
gh release download "${GITHUB_REF#refs/tags/}" --dir dist \
|
||||
--pattern '*.tar.gz' \
|
||||
--pattern '*.zip' \
|
||||
--pattern '*.deb' \
|
||||
--pattern '*.rpm' \
|
||||
--pattern '*.apk'
|
||||
|
||||
- name: VirusTotal scan + update release body
|
||||
uses: crazy-max/ghaction-virustotal@936d8c5c00afe97d3d9a1af26d017cfdf26800a2 # v5
|
||||
with:
|
||||
vt_api_key: ${{ secrets.VT_API_KEY }}
|
||||
files: |
|
||||
./dist/*.tar.gz
|
||||
./dist/*.zip
|
||||
./dist/*.deb
|
||||
./dist/*.rpm
|
||||
./dist/*.apk
|
||||
update_release_body: true
|
||||
@@ -0,0 +1,64 @@
|
||||
name: Scorecard supply-chain security
|
||||
|
||||
# OpenSSF Scorecard — automated grading across ~18 security dimensions
|
||||
# (branch protection, pinned deps, SAST, code review, signed releases, etc.).
|
||||
# Runs weekly + on push to main. Publishes results as SARIF to the Security
|
||||
# tab and to the public api.scorecard.dev aggregator that feeds the badge
|
||||
# in README.md.
|
||||
#
|
||||
# Template source: https://github.com/ossf/scorecard-action
|
||||
|
||||
on:
|
||||
branch_protection_rule:
|
||||
# Weekly schedule keeps the badge fresh and surfaces drift (new
|
||||
# unpinned deps, missing branch protection changes, etc.)
|
||||
schedule:
|
||||
- cron: '32 5 * * 1'
|
||||
push:
|
||||
branches: [main]
|
||||
|
||||
permissions: read-all
|
||||
|
||||
jobs:
|
||||
analysis:
|
||||
name: Scorecard analysis
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
# Needed to upload SARIF results to the Security tab.
|
||||
security-events: write
|
||||
# Needed to publish results to the public Scorecard API so the
|
||||
# shield badge in README.md resolves.
|
||||
id-token: write
|
||||
contents: read
|
||||
actions: read
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Run analysis
|
||||
uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3
|
||||
with:
|
||||
results_file: results.sarif
|
||||
results_format: sarif
|
||||
# publish_results: true is what makes the public badge work.
|
||||
# Requires the repo to be public; fails silently otherwise.
|
||||
publish_results: true
|
||||
|
||||
# Upload to the Actions artifact store so the results are viewable
|
||||
# even before the SARIF lands in the Security tab (GitHub batches
|
||||
# SARIF ingestion on a ~15min cadence).
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
||||
with:
|
||||
name: SARIF file
|
||||
path: results.sarif
|
||||
retention-days: 5
|
||||
|
||||
# SARIF into the Security tab so findings show up per-PR.
|
||||
- name: Upload to code-scanning
|
||||
uses: github/codeql-action/upload-sarif@54f647b7e1bb85c95cddabcd46b0c578ec92bc1a # v4
|
||||
with:
|
||||
sarif_file: results.sarif
|
||||
@@ -0,0 +1,48 @@
|
||||
name: security
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
schedule:
|
||||
- cron: '0 6 * * *'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
security-events: write
|
||||
|
||||
jobs:
|
||||
govulncheck:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
- uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
cache: true
|
||||
- name: govulncheck
|
||||
uses: golang/govulncheck-action@b625fbe08f3bccbe446d94fbf87fcc875a4f50ee # v1
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
repo-checkout: false
|
||||
|
||||
trivy-fs:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
- uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0
|
||||
with:
|
||||
scan-type: fs
|
||||
scan-ref: .
|
||||
severity: CRITICAL,HIGH
|
||||
exit-code: '1'
|
||||
ignore-unfixed: true
|
||||
format: sarif
|
||||
output: trivy-fs.sarif
|
||||
- if: always()
|
||||
uses: github/codeql-action/upload-sarif@54f647b7e1bb85c95cddabcd46b0c578ec92bc1a # v4
|
||||
with:
|
||||
sarif_file: trivy-fs.sarif
|
||||
@@ -0,0 +1,46 @@
|
||||
name: Skill drift
|
||||
|
||||
# Fences every agent integration's generated output against what is
|
||||
# checked in — across all platforms (not Claude-only), on every PR (not
|
||||
# release-only). Two complementary checks run:
|
||||
#
|
||||
# 1. The all-platform render golden (go test) byte-compares every
|
||||
# adapter's rendered MCP config / instructions / hooks / routing
|
||||
# blocks against committed goldens.
|
||||
# 2. A structural --check that every adapter still emits a gortex
|
||||
# registration.
|
||||
#
|
||||
# Both build the pure-Go CLI (no -tags llama), so the gate runs on a
|
||||
# stock runner. When a check fails on an intended change, regenerate and
|
||||
# commit the goldens:
|
||||
# go test ./cmd/gortex -run TestAgentsRenderGolden -update-agent-render
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [main]
|
||||
paths:
|
||||
- 'internal/agents/**'
|
||||
- 'internal/hooks/**'
|
||||
- 'internal/mcp/**'
|
||||
- 'cmd/gortex/**'
|
||||
- '.github/workflows/skill-drift.yml'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
skill-drift:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
|
||||
- uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
|
||||
- name: Build CLI
|
||||
run: go build -o gortex ./cmd/gortex/
|
||||
|
||||
- name: All-platform render golden
|
||||
run: go test ./cmd/gortex -run TestAgentsRenderGolden -count=1
|
||||
|
||||
- name: All-platform render structural check
|
||||
run: ./gortex agents render --check
|
||||
Reference in New Issue
Block a user