chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 12:01:57 +08:00
commit 9dda3e2451
399 changed files with 118131 additions and 0 deletions
+140
View File
@@ -0,0 +1,140 @@
# CI/CD Flows
## PR Quality Gates (ci.yml)
Trigger: pull_request to develop or master
```
┌──────────────────┐
│ PR opened │
└────────┬─────────┘
┌────────▼─────────┐
│ fmt --all │
└────────┬─────────┘
┌───────────▼──────────┐
│ clippy --all-targets │
└───┬───┬───┬───┬───┬──┘
│ │ │ │ │
┌───────────────┘ │ │ │ └────────────────┐
│ ┌───────────┘ │ └───────────┐ │
▼ ▼ ▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌───────────┐ ┌─────────┐ ┌──────────┐
│ test │ │ security │ │ semgrep │ │benchmark│ │ doc │
│ ubuntu │ │ cargo │ │ AST-aware │ │ >=80% │ │ review │
│ windows │ │ audit │ │ diff-only │ │ savings │ │ ai agent │
│ macos │ │ patterns │ │ │ │ │ │ │
└────┬─────┘ └────┬─────┘ └─────┬─────┘ └────┬────┘ └────┬─────┘
│ │ │ │ │
└────────────┴─────────┬───┴─────────────┴────────────┘
┌──────────▼─────────┐
│ All must pass │
│ to merge │
└────────────────────┘
+ DCO check (independent, develop PRs only)
+ Dependabot (weekly: Cargo deps + GitHub Actions)
```
## Merge to develop — pre-release (cd.yml)
Trigger: push to develop | workflow_dispatch (not master) | Concurrency: cancel-in-progress
```
┌──────────────────┐
│ push to develop │
│ OR dispatch │
└────────┬─────────┘
┌────────▼──────────────────┐
│ pre-release │
│ compute next version │
│ from conventional commits │
│ tag = v{next}-rc.{run} │
└────────┬──────────────────┘
┌────────▼──────────────────┐
│ release.yml │
│ prerelease = true │
└────────┬──────────────────┘
┌────────▼──────────────────┐
│ Build │
│ 5 platforms + DEB + RPM │
└────────┬──────────────────┘
┌────────▼──────────────────┐
│ GitHub Release │
│ (pre-release badge) │
│ │
│ Discord: SKIPPED │
│ Homebrew: SKIPPED │
└──────────────────────────┘
```
## Merge to master — stable release (cd.yml)
Trigger: push to master (only) | Concurrency: never cancelled
```
┌──────────────────┐
│ push to master │
└────────┬─────────┘
┌────────▼──────────────────┐
│ release-please │
│ analyze conventional │
│ commits │
└────────┬──────────────────┘
┌────┴────────────────┐
│ │
no release release created
│ │
▼ ▼
┌──────────────┐ ┌───────────────────────┐
│ create/update│ │ release.yml │
│ release PR │ │ prerelease = false │
└──────────────┘ └───────────┬───────────┘
┌────────────▼────────────┐
│ Build │
│ 5 platforms + DEB + RPM │
└────────────┬────────────┘
┌────────────▼────────────┐
│ GitHub Release │
│ (stable, "Latest" badge) │
└──┬─────────┬─────────┬──┘
│ │ │
▼ ▼ ▼
Discord Homebrew latest
notify tap update tag
```
## Manual release (release.yml)
Trigger: workflow_dispatch
```
┌────────────────────────┐
│ workflow_dispatch │
│ inputs: tag, prerelease │
└───────────┬────────────┘
┌───────────▼────────────┐
│ Full build pipeline │
│ 5 platforms + DEB + RPM │
└───────────┬────────────┘
┌──────┴──────┐
│ │
prerelease=false prerelease=true
│ │
▼ ▼
Discord pre-release
Homebrew badge only
latest tag
```
+156
View File
@@ -0,0 +1,156 @@
name: CD
on:
workflow_dispatch:
push:
branches: [develop, master]
concurrency:
group: cd-${{ github.ref }}
cancel-in-progress: ${{ github.ref != 'refs/heads/master' }}
permissions:
contents: write
pull-requests: write
jobs:
# ═══════════════════════════════════════════════
# DEVELOP PATH: Pre-release
# ═══════════════════════════════════════════════
pre-release:
if: >-
github.ref == 'refs/heads/develop'
|| (github.event_name == 'workflow_dispatch' && github.ref != 'refs/heads/master')
runs-on: ubuntu-latest
outputs:
tag: ${{ steps.tag.outputs.tag }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
fetch-tags: true
- name: Compute version from commits like release please
id: tag
run: |
LATEST_TAG=$(git tag -l 'v[0-9]*.[0-9]*.[0-9]*' --sort=-version:refname | grep -v '-' | head -1)
if [ -z "$LATEST_TAG" ]; then
echo "::error::No stable release tag found"
exit 1
fi
LATEST_VERSION="${LATEST_TAG#v}"
echo "Latest release: $LATEST_TAG"
# ── Analyse conventional commits since that tag ──
COMMITS=$(git log "${LATEST_TAG}..HEAD" --format="%s")
HAS_BREAKING=$(echo "$COMMITS" | grep -cE '^[a-z]+(\(.+\))?!:' || true)
HAS_FEAT=$(echo "$COMMITS" | grep -cE '^feat(\(.+\))?:' || true)
HAS_FIX=$(echo "$COMMITS" | grep -cE '^fix(\(.+\))?:' || true)
echo "Commits since ${LATEST_TAG} — breaking=$HAS_BREAKING feat=$HAS_FEAT fix=$HAS_FIX"
# ── Compute next version (matches release-please observed behaviour) ──
# Pre-1.0 with bump-minor-pre-major: breaking → minor, feat → minor, fix → patch
IFS='.' read -r MAJOR MINOR PATCH <<< "$LATEST_VERSION"
if [ "$MAJOR" -eq 0 ]; then
if [ "$HAS_BREAKING" -gt 0 ] || [ "$HAS_FEAT" -gt 0 ]; then
MINOR=$((MINOR + 1)); PATCH=0 # breaking or feat → minor
else
PATCH=$((PATCH + 1)) # fix only → patch
fi
else
if [ "$HAS_BREAKING" -gt 0 ]; then
MAJOR=$((MAJOR + 1)); MINOR=0; PATCH=0 # breaking → major
elif [ "$HAS_FEAT" -gt 0 ]; then
MINOR=$((MINOR + 1)); PATCH=0 # feat → minor
else
PATCH=$((PATCH + 1)) # fix → patch
fi
fi
VERSION="${MAJOR}.${MINOR}.${PATCH}"
TAG="dev-${VERSION}-rc.${{ github.run_number }}"
echo "Next version: $VERSION (from $LATEST_VERSION)"
echo "Pre-release tag: $TAG"
# Safety: fail if this exact tag already exists
if git ls-remote --tags origin "refs/tags/${TAG}" | grep -q .; then
echo "::error::Tag ${TAG} already exists"
exit 1
fi
echo "tag=$TAG" >> $GITHUB_OUTPUT
build-prerelease:
name: Build pre-release
needs: pre-release
if: needs.pre-release.outputs.tag != ''
uses: ./.github/workflows/release.yml
with:
tag: ${{ needs.pre-release.outputs.tag }}
prerelease: true
permissions:
contents: write
secrets: inherit
# ═══════════════════════════════════════════════
# MASTER PATH: Full release
# ═══════════════════════════════════════════════
release-please:
if: github.ref == 'refs/heads/master' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch')
runs-on: ubuntu-latest
outputs:
release_created: ${{ steps.release.outputs.release_created }}
tag_name: ${{ steps.release.outputs.tag_name }}
steps:
- uses: actions/create-github-app-token@v3
id: app-token
with:
client-id: ${{ secrets.APP_CLIENT_ID }}
private-key: ${{ secrets.APP_PRIVATE_KEY }}
permission-contents: write
permission-pull-requests: write
- uses: googleapis/release-please-action@v4
id: release
with:
release-type: rust
package-name: rtk
token: ${{ steps.app-token.outputs.token }}
target-branch: ${{ github.ref_name }}
build-release:
name: Build and upload release assets
needs: release-please
if: ${{ needs.release-please.outputs.release_created == 'true' }}
uses: ./.github/workflows/release.yml
with:
tag: ${{ needs.release-please.outputs.tag_name }}
permissions:
contents: write
secrets: inherit
update-latest-tag:
name: Update 'latest' tag
needs: [release-please, build-release]
if: ${{ needs.release-please.outputs.release_created == 'true' }}
runs-on: ubuntu-latest
steps:
- uses: actions/create-github-app-token@v3
id: app-token
with:
client-id: ${{ secrets.APP_CLIENT_ID }}
private-key: ${{ secrets.APP_PRIVATE_KEY }}
permission-contents: write
- uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ steps.app-token.outputs.token }}
- name: Update latest tag
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git tag -fa latest -m "Latest stable release (${{ needs.release-please.outputs.tag_name }})"
git push origin latest --force
+389
View File
@@ -0,0 +1,389 @@
name: CI
on:
pull_request:
branches: [develop, master]
permissions:
contents: read
pull-requests: read
env:
CARGO_TERM_COLOR: always
jobs:
# ─── Fast gates (fail early, save CI minutes) ───
check-test-presence:
name: test presence
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 50
- name: Check filter modules have tests
run: |
git fetch origin "${{ github.base_ref }}" --depth=1 || true
bash scripts/check-test-presence.sh "origin/${{ github.base_ref }}"
fmt:
name: fmt
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt
- run: cargo fmt --all -- --check
clippy:
name: clippy
needs: fmt
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
components: clippy
- uses: Swatinem/rust-cache@v2
- run: cargo clippy --all-targets
# ─── Parallel gates (all need code to compile) ───
test:
name: test (${{ matrix.os }})
needs: clippy
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- run: cargo test --all
security:
name: Security Scan
needs: clippy
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- name: Install cargo-audit
run: cargo install cargo-audit
- name: Cargo Audit (CVE check)
run: |
echo "## Security Scan Results" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### Dependency Vulnerabilities" >> $GITHUB_STEP_SUMMARY
if cargo audit 2>&1 | tee audit.log; then
echo "No known vulnerabilities detected" >> $GITHUB_STEP_SUMMARY
else
echo "Vulnerabilities found:" >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
cat audit.log >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
echo "::warning::Dependency vulnerabilities detected - review required"
fi
echo "" >> $GITHUB_STEP_SUMMARY
- name: Critical files check
run: |
echo "### Critical Files Modified" >> $GITHUB_STEP_SUMMARY
CRITICAL=$(git diff --name-only origin/master...HEAD | grep -E "(runner|summary|tracking|init|pnpm_cmd|container)\.rs|Cargo\.toml|workflows/.*\.yml" || true)
if [ -n "$CRITICAL" ]; then
echo "**HIGH RISK**: The following critical files were modified:" >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
echo "$CRITICAL" >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Required Actions:**" >> $GITHUB_STEP_SUMMARY
echo "- [ ] Manual security review by 2 maintainers" >> $GITHUB_STEP_SUMMARY
echo "- [ ] Verify no shell injection vectors" >> $GITHUB_STEP_SUMMARY
echo "- [ ] Check input validation remains intact" >> $GITHUB_STEP_SUMMARY
echo "::warning::Critical RTK files modified - enhanced review required"
else
echo "No critical files modified" >> $GITHUB_STEP_SUMMARY
fi
echo "" >> $GITHUB_STEP_SUMMARY
- name: Dangerous patterns scan
run: |
echo "### Dangerous Code Patterns" >> $GITHUB_STEP_SUMMARY
PATTERNS=$(git diff origin/master...HEAD | grep -E "Command::new\(\"sh\"|Command::new\(\"bash\"|\.env\(\"LD_PRELOAD|\.env\(\"PATH|reqwest::|std::net::|TcpStream|UdpSocket|unsafe \{|\.unwrap\(\) |panic!\(|todo!\(|unimplemented!\(" || true)
if [ -n "$PATTERNS" ]; then
echo "**Potentially dangerous patterns detected:**" >> $GITHUB_STEP_SUMMARY
echo '```diff' >> $GITHUB_STEP_SUMMARY
echo "$PATTERNS" | head -30 >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Security Concerns:**" >> $GITHUB_STEP_SUMMARY
echo "$PATTERNS" | grep -q "Command::new" && echo "- Shell command execution detected" >> $GITHUB_STEP_SUMMARY || true
echo "$PATTERNS" | grep -q "\.env\(\"" && echo "- Environment variable manipulation" >> $GITHUB_STEP_SUMMARY || true
echo "$PATTERNS" | grep -q "reqwest::\|std::net::\|TcpStream\|UdpSocket" && echo "- Network operations added" >> $GITHUB_STEP_SUMMARY || true
echo "$PATTERNS" | grep -q "unsafe" && echo "- Unsafe code blocks" >> $GITHUB_STEP_SUMMARY || true
echo "$PATTERNS" | grep -q "\.unwrap\(\)\|panic!\(" && echo "- Panic-inducing code" >> $GITHUB_STEP_SUMMARY || true
echo "::warning::Dangerous code patterns detected - manual review required"
else
echo "No dangerous patterns detected" >> $GITHUB_STEP_SUMMARY
fi
echo "" >> $GITHUB_STEP_SUMMARY
- name: New dependencies check
run: |
echo "### Dependencies Changes" >> $GITHUB_STEP_SUMMARY
if git diff origin/master...HEAD Cargo.toml | grep -E "^\+.*=" | grep -v "^\+\+\+" > new_deps.txt; then
echo "**New dependencies added:**" >> $GITHUB_STEP_SUMMARY
echo '```toml' >> $GITHUB_STEP_SUMMARY
cat new_deps.txt >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Required Actions:**" >> $GITHUB_STEP_SUMMARY
echo "- [ ] Audit each new dependency on crates.io" >> $GITHUB_STEP_SUMMARY
echo "- [ ] Check maintainer reputation and download counts" >> $GITHUB_STEP_SUMMARY
echo "- [ ] Verify no typosquatting (e.g., 'reqwest' vs 'request')" >> $GITHUB_STEP_SUMMARY
echo "::warning::New dependencies require supply chain audit"
else
echo "No new dependencies added" >> $GITHUB_STEP_SUMMARY
fi
echo "" >> $GITHUB_STEP_SUMMARY
- name: Clippy security lints
run: |
echo "### Clippy Security Lints" >> $GITHUB_STEP_SUMMARY
if cargo clippy --all-targets -- -W clippy::unwrap_used -W clippy::panic -W clippy::expect_used 2>&1 | tee clippy.log | grep -E "warning:|error:"; then
echo "Security-related lints triggered:" >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
grep -E "warning:|error:" clippy.log | head -20 >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
echo "::warning::Clippy security lints failed"
else
echo "All security lints passed" >> $GITHUB_STEP_SUMMARY
fi
- name: Summary verdict
run: |
echo "---" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### Security Review Verdict" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**This is an automated security scan. A human maintainer must:**" >> $GITHUB_STEP_SUMMARY
echo "1. Review all warnings above" >> $GITHUB_STEP_SUMMARY
echo "2. Verify PR intent matches actual code changes" >> $GITHUB_STEP_SUMMARY
echo "3. Check for subtle backdoors or logic bombs" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**For high-risk PRs (critical files modified):**" >> $GITHUB_STEP_SUMMARY
echo "- Require approval from 2 maintainers" >> $GITHUB_STEP_SUMMARY
echo "- Test in isolated environment before merge" >> $GITHUB_STEP_SUMMARY
semgrep:
name: semgrep security scan
needs: clippy
runs-on: ubuntu-latest
container:
image: semgrep/semgrep
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- run: semgrep scan --config .semgrep.yml --baseline-commit ${{ github.event.pull_request.base.sha }} --error
benchmark:
name: benchmark
needs: clippy
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- name: Build rtk
run: cargo build --release
- name: Install system tools
run: sudo apt-get install -y tree
- name: Install Python tools
run: pip install ruff pytest mypy
- name: Install Go
uses: actions/setup-go@v5
with:
go-version: "stable"
- name: Install Go tools
run: go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
- name: Run benchmark
run: ./scripts/benchmark.sh
# ─── AI Doc Review: develop PRs only ───
doc-review:
name: doc review
if: github.base_ref == 'develop'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Gather PR context
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
PR_NUM=${{ github.event.pull_request.number }}
gh pr diff "$PR_NUM" --name-only > changed_files.txt
gh pr diff "$PR_NUM" | head -c 12000 > diff.txt
gh pr view "$PR_NUM" --json title,body --jq '"PR Title: \(.title)\nPR Description: \(.body)"' > pr_info.txt
- name: Build prompt files
run: |
# System prompt
cat <<'EOF' > system_prompt.txt
You are a documentation reviewer for the RTK project.
You will receive the project's CONTRIBUTING.md (which contains the documentation rules), the PR info, changed files, and diff.
Your job: based ONLY on the documentation rules in CONTRIBUTING.md, decide if the PR includes the required documentation updates.
IMPORTANT:
- CI/CD changes, test-only changes, and refactors with no user-facing impact do NOT require doc updates.
- Be practical, not pedantic. Small obvious fixes don't need CHANGELOG entries.
- Only flag missing docs when there is a clear user-facing change.
EOF
# User prompt: concatenate files (no printf, no variable expansion issues)
{
cat pr_info.txt
echo ""
echo "---"
echo "CONTRIBUTING.md:"
cat CONTRIBUTING.md
echo ""
echo "---"
echo "Changed files:"
cat changed_files.txt
echo ""
echo "---"
echo "Diff (may be truncated):"
cat diff.txt
} > user_prompt.txt
- name: AI documentation review
env:
ANTHROPIC_API_KEY: ${{ secrets.RTK_DOCS_ANTHROPIC_KEY }}
run: |
echo "## Documentation Review (AI)" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
if [ -z "$ANTHROPIC_API_KEY" ]; then
echo "::warning::ANTHROPIC_API_KEY not configured — skipping AI doc review"
echo "Skipped: ANTHROPIC_API_KEY secret not configured." >> $GITHUB_STEP_SUMMARY
exit 0
fi
echo "::group::Preparing API request"
echo "System prompt: $(wc -c < system_prompt.txt) bytes"
echo "User prompt: $(wc -c < user_prompt.txt) bytes"
SYSTEM_JSON=$(jq -Rs . < system_prompt.txt)
USER_JSON=$(jq -Rs . < user_prompt.txt)
echo "::endgroup::"
echo "::group::Calling Claude API (claude-sonnet-4-6)"
RESPONSE=$(curl -s -w "\n%{http_code}" https://api.anthropic.com/v1/messages \
-H "content-type: application/json" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-d "{
\"model\": \"claude-sonnet-4-6\",
\"max_tokens\": 1024,
\"messages\": [{\"role\": \"user\", \"content\": $USER_JSON}],
\"system\": $SYSTEM_JSON,
\"output_config\": {
\"format\": {
\"type\": \"json_schema\",
\"schema\": {
\"type\": \"object\",
\"properties\": {
\"status\": {\"type\": \"string\", \"enum\": [\"PASS\", \"FAIL\"]},
\"reasoning\": {\"type\": \"array\", \"items\": {\"type\": \"string\"}},
\"files_to_update\": {\"type\": \"array\", \"items\": {\"type\": \"string\"}}
},
\"required\": [\"status\", \"reasoning\", \"files_to_update\"],
\"additionalProperties\": false
}
}
}
}")
HTTP_CODE=$(echo "$RESPONSE" | tail -1)
BODY=$(echo "$RESPONSE" | sed '$d')
echo "HTTP status: $HTTP_CODE"
echo "::endgroup::"
if [ "$HTTP_CODE" != "200" ]; then
echo "::warning::Claude API returned HTTP $HTTP_CODE — skipping doc review"
echo "Skipped: API error (HTTP $HTTP_CODE)" >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
echo "$BODY" | head -10 >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
exit 0
fi
# Parse structured JSON response
REVIEW_JSON=$(echo "$BODY" | jq -r '.content[0].text // empty')
if [ -z "$REVIEW_JSON" ]; then
echo "::warning::Empty response from Claude API — skipping doc review"
echo "Skipped: empty API response" >> $GITHUB_STEP_SUMMARY
echo "Raw response:"
echo "$BODY" | head -20
exit 0
fi
echo "::group::AI Review Result"
echo "$REVIEW_JSON" | jq .
echo "::endgroup::"
STATUS=$(echo "$REVIEW_JSON" | jq -r '.status')
REASONING=$(echo "$REVIEW_JSON" | jq -r '.reasoning[]' 2>/dev/null)
FILES=$(echo "$REVIEW_JSON" | jq -r '.files_to_update[]' 2>/dev/null)
echo "### Verdict: ${STATUS}" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
if [ -n "$REASONING" ]; then
echo "**Reasoning:**" >> $GITHUB_STEP_SUMMARY
echo "$REASONING" | while IFS= read -r line; do
echo "- $line" >> $GITHUB_STEP_SUMMARY
done
echo "" >> $GITHUB_STEP_SUMMARY
fi
if [ "$STATUS" = "FAIL" ] && [ -n "$FILES" ]; then
echo "**Files to update:**" >> $GITHUB_STEP_SUMMARY
echo "$FILES" | while IFS= read -r f; do
echo "- \`$f\`" >> $GITHUB_STEP_SUMMARY
done
echo "" >> $GITHUB_STEP_SUMMARY
fi
if [ "$STATUS" = "PASS" ]; then
echo "Documentation review passed."
elif [ "$STATUS" = "FAIL" ]; then
echo "::error::Documentation review failed — see summary for details"
exit 1
else
echo "::warning::Unexpected status '${STATUS}' — skipping"
echo "Unexpected AI response status: ${STATUS}" >> $GITHUB_STEP_SUMMARY
fi
+126
View File
@@ -0,0 +1,126 @@
name: Update Next Release PR
on:
pull_request_target:
types: [closed]
branches: [develop]
permissions:
contents: read
pull-requests: write
jobs:
update-next-release:
if: github.event.pull_request.merged == true
runs-on: ubuntu-latest
steps:
- name: Update Next Release PR
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_NUMBER: ${{ github.event.pull_request.number }}
PR_TITLE: ${{ github.event.pull_request.title }}
PR_URL: ${{ github.event.pull_request.html_url }}
PR_BODY: ${{ github.event.pull_request.body }}
REPO: ${{ github.repository }}
ALLOWED_REPOS: "rtk-ai/rtk"
run: |
set -euo pipefail
URL_PATTERN=""
for repo in $ALLOWED_REPOS; do
URL_PATTERN="${URL_PATTERN}|https://github\\.com/${repo}/issues/[0-9]+"
done
URL_PATTERN="${URL_PATTERN#|}"
if printf '%s' "$PR_TITLE" | grep -qiE '^feat'; then
SECTION="Feats"
elif printf '%s' "$PR_TITLE" | grep -qiE '^fix'; then
SECTION="Fix"
else
SECTION="Other"
fi
ISSUE_REFS=""
if [ -n "$PR_BODY" ]; then
ISSUE_REFS=$(echo "$PR_BODY" \
| grep -oiE "(closes|fixes|resolves):?\s+#[0-9]+|${URL_PATTERN}" \
| grep -oE '#[0-9]+|issues/[0-9]+' \
| sed 's|issues/|#|' \
| sort -u \
|| true)
fi
ENTRY="- ${PR_TITLE} [#${PR_NUMBER}](${PR_URL})"
if [ -n "$ISSUE_REFS" ]; then
CLOSES_PARTS=""
while IFS= read -r ref; do
[ -z "$ref" ] && continue
NUM="${ref#\#}"
ISSUE_URL="https://github.com/${REPO}/issues/${NUM}"
if [ -n "$CLOSES_PARTS" ]; then
CLOSES_PARTS="${CLOSES_PARTS}, [${ref}](${ISSUE_URL}) (to verify)"
else
CLOSES_PARTS="Closes [${ref}](${ISSUE_URL}) (to verify)"
fi
done <<< "$ISSUE_REFS"
ENTRY="${ENTRY} — ${CLOSES_PARTS}"
fi
NEXT_PR=$(gh pr list \
--repo "$REPO" \
--label next-release \
--base master \
--head develop \
--state open \
--json number,body \
--jq '.[0] // empty')
NEXT_PR_NUMBER=""
if [ -n "$NEXT_PR" ]; then
NEXT_PR_NUMBER=$(echo "$NEXT_PR" | jq -r '.number')
fi
if [ -z "$NEXT_PR_NUMBER" ]; then
TEMPLATE="### Feats
### Fix
### Other"
PR_CREATE_URL=$(gh pr create \
--repo "$REPO" \
--base master \
--head develop \
--title "Next Release" \
--label next-release \
--body "$TEMPLATE")
NEXT_PR_NUMBER=$(echo "$PR_CREATE_URL" | grep -oE '/pull/[0-9]+' | grep -oE '[0-9]+')
CURRENT_BODY="$TEMPLATE"
else
CURRENT_BODY=$(echo "$NEXT_PR" | jq -r '.body')
fi
SECTION_HEADER="### ${SECTION}"
export ENTRY
if echo "$CURRENT_BODY" | grep -qF "$SECTION_HEADER"; then
UPDATED_BODY=$(echo "$CURRENT_BODY" | awk -v section="$SECTION_HEADER" '
$0 == section {
print
print ENVIRON["ENTRY"]
next
}
{ print }
')
else
UPDATED_BODY="${CURRENT_BODY}
${SECTION_HEADER}
${ENTRY}"
fi
gh pr edit "$NEXT_PR_NUMBER" \
--repo "$REPO" \
--body "$UPDATED_BODY"
echo "Updated Next Release PR #${NEXT_PR_NUMBER} — added entry to ### ${SECTION}"
+48
View File
@@ -0,0 +1,48 @@
name: PR Target Branch Check
on:
pull_request_target:
types: [opened, edited]
jobs:
check-target:
runs-on: ubuntu-latest
permissions: {}
# Skip develop→master PRs (maintainer releases)
if: >-
github.event.pull_request.base.ref == 'master' &&
github.event.pull_request.head.ref != 'develop'
steps:
- uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
id: app-token
with:
client-id: ${{ secrets.APP_CLIENT_ID }}
private-key: ${{ secrets.APP_PRIVATE_KEY }}
permission-pull-requests: write
- name: Add wrong-base label and comment
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7
with:
github-token: ${{ steps.app-token.outputs.token }}
script: |
const pr = context.payload.pull_request;
// Add label
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number,
labels: ['wrong-base']
});
// Post comment
const body = `Automatic message from CI checks : It seems like this branch is targeting the wrong branch, any contribution should target develop branch.
See [CONTRIBUTING.md](https://github.com/rtk-ai/rtk/blob/master/CONTRIBUTING.md) for details.`;
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number,
body: body
});
+370
View File
@@ -0,0 +1,370 @@
name: Release
on:
workflow_call:
inputs:
tag:
description: 'Tag to release'
required: true
type: string
prerelease:
description: 'Mark as pre-release'
required: false
type: boolean
default: false
workflow_dispatch:
inputs:
tag:
description: 'Tag to release (e.g., v0.1.0)'
required: true
prerelease:
description: 'Mark as pre-release'
type: boolean
default: false
permissions:
contents: write
env:
CARGO_TERM_COLOR: always
jobs:
build:
name: Build ${{ matrix.target }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
# macOS
- target: x86_64-apple-darwin
os: macos-latest
archive: tar.gz
- target: aarch64-apple-darwin
os: macos-latest
archive: tar.gz
# Linux
- target: x86_64-unknown-linux-musl
os: ubuntu-latest
archive: tar.gz
musl: true
- target: aarch64-unknown-linux-gnu
os: ubuntu-latest
archive: tar.gz
cross: true
# Windows
- target: x86_64-pc-windows-msvc
os: windows-latest
archive: zip
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.target }}
- name: Install cross-compilation tools
if: matrix.cross
run: |
sudo apt-get update
sudo apt-get install -y gcc-aarch64-linux-gnu
echo "CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc" >> $GITHUB_ENV
- name: Install musl tools
if: matrix.musl
run: |
sudo apt-get update
sudo apt-get install -y musl-tools
- name: Build
run: cargo build --release --target ${{ matrix.target }}
env:
RTK_TELEMETRY_URL: ${{ vars.RTK_TELEMETRY_URL }}
RTK_TELEMETRY_TOKEN: ${{ secrets.RTK_TELEMETRY_TOKEN }}
- name: Package (Unix)
if: matrix.os != 'windows-latest'
run: |
cd target/${{ matrix.target }}/release
tar -czvf ../../../rtk-${{ matrix.target }}.${{ matrix.archive }} rtk
cd ../../..
- name: Package (Windows)
if: matrix.os == 'windows-latest'
run: |
cd target/${{ matrix.target }}/release
7z a ../../../rtk-${{ matrix.target }}.${{ matrix.archive }} rtk.exe
cd ../../..
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: rtk-${{ matrix.target }}
path: rtk-${{ matrix.target }}.${{ matrix.archive }}
build-deb:
name: Build DEB package
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
- name: Install cargo-deb
run: cargo install cargo-deb
- name: Build DEB
run: cargo deb
env:
RTK_TELEMETRY_URL: ${{ vars.RTK_TELEMETRY_URL }}
RTK_TELEMETRY_TOKEN: ${{ secrets.RTK_TELEMETRY_TOKEN }}
- name: Upload DEB
uses: actions/upload-artifact@v4
with:
name: rtk-deb
path: target/debian/*.deb
build-rpm:
name: Build RPM package
runs-on: ubuntu-latest
container: fedora:latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install dependencies
run: |
dnf install -y rust cargo rpm-build
- name: Install cargo-generate-rpm
run: cargo install cargo-generate-rpm
- name: Build release
run: cargo build --release
env:
RTK_TELEMETRY_URL: ${{ vars.RTK_TELEMETRY_URL }}
RTK_TELEMETRY_TOKEN: ${{ secrets.RTK_TELEMETRY_TOKEN }}
- name: Generate RPM
run: cargo generate-rpm
- name: Upload RPM
uses: actions/upload-artifact@v4
with:
name: rtk-rpm
path: target/generate-rpm/*.rpm
release:
name: Create Release
needs: [build, build-deb, build-rpm]
runs-on: ubuntu-latest
steps:
- uses: actions/create-github-app-token@v3
id: app-token
with:
client-id: ${{ secrets.APP_CLIENT_ID }}
private-key: ${{ secrets.APP_PRIVATE_KEY }}
permission-contents: write
- name: Checkout
uses: actions/checkout@v4
- name: Download all artifacts
uses: actions/download-artifact@v4
with:
path: artifacts
- name: Get version
id: version
env:
INPUT_TAG: ${{ inputs.tag }}
RELEASE_TAG: ${{ github.event.release.tag_name }}
run: |
TAG="$INPUT_TAG"
if [ -z "$TAG" ]; then
TAG="$RELEASE_TAG"
fi
echo "version=$TAG" >> $GITHUB_OUTPUT
- name: Flatten artifacts
run: |
mkdir -p release
find artifacts -type f \( -name "*.tar.gz" -o -name "*.zip" -o -name "*.deb" -o -name "*.rpm" \) -exec cp {} release/ \;
- name: Create version-agnostic package names
run: |
cd release
for f in *.deb; do
[ -f "$f" ] && cp "$f" "rtk_amd64.deb"
done
for f in *.rpm; do
[ -f "$f" ] && cp "$f" "rtk.x86_64.rpm"
done
- name: Create checksums
run: |
cd release
sha256sum * > checksums.txt
- name: Upload Release Assets
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ steps.version.outputs.version }}
files: release/*
prerelease: ${{ inputs.prerelease }}
token: ${{ steps.app-token.outputs.token }}
notify-discord:
name: Notify Discord
needs: [release]
if: ${{ !inputs.prerelease }}
runs-on: ubuntu-latest
steps:
- name: Get version
id: version
env:
INPUT_TAG: ${{ inputs.tag }}
RELEASE_TAG: ${{ github.event.release.tag_name }}
run: |
TAG="$INPUT_TAG"
if [ -z "$TAG" ]; then
TAG="$RELEASE_TAG"
fi
echo "tag=$TAG" >> $GITHUB_OUTPUT
- name: Send Discord notification
env:
DISCORD_WEBHOOK: ${{ secrets.RTK_DISCORD_RELEASE }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
TAG="${{ steps.version.outputs.tag }}"
RELEASE_URL="https://github.com/rtk-ai/rtk/releases/tag/${TAG}"
# Fetch release notes from GitHub API
NOTES=$(gh api "repos/rtk-ai/rtk/releases/tags/${TAG}" --jq '.body' 2>/dev/null | head -c 1800 || echo "")
DESC=$(echo "${NOTES:-No release notes}" | jq -Rs .)
jq -n \
--arg title "RTK ${TAG} released" \
--arg url "$RELEASE_URL" \
--argjson desc "$DESC" \
'{embeds: [{title: $title, url: $url, description: $desc, color: 5814783, footer: {text: "Rust Token Killer"}}]}' \
| curl -sf -H "Content-Type: application/json" -d @- "$DISCORD_WEBHOOK"
homebrew:
name: Update Homebrew formula
needs: [release]
if: ${{ !inputs.prerelease }}
runs-on: ubuntu-latest
steps:
- name: Get version
id: version
env:
INPUT_TAG: ${{ inputs.tag }}
RELEASE_TAG: ${{ github.event.release.tag_name }}
run: |
TAG="$INPUT_TAG"
if [ -z "$TAG" ]; then
TAG="$RELEASE_TAG"
fi
VERSION="${TAG#v}"
echo "tag=$TAG" >> $GITHUB_OUTPUT
echo "version=$VERSION" >> $GITHUB_OUTPUT
- name: Download checksums
run: |
gh release download "${{ steps.version.outputs.tag }}" \
--repo rtk-ai/rtk \
--pattern checksums.txt
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Parse checksums
id: sha
run: |
echo "mac_arm=$(grep aarch64-apple-darwin.tar.gz checksums.txt | head -1 | awk '{print $1}')" >> $GITHUB_OUTPUT
echo "mac_intel=$(grep x86_64-apple-darwin.tar.gz checksums.txt | head -1 | awk '{print $1}')" >> $GITHUB_OUTPUT
echo "linux_arm=$(grep aarch64-unknown-linux-gnu.tar.gz checksums.txt | head -1 | awk '{print $1}')" >> $GITHUB_OUTPUT
echo "linux_intel=$(grep x86_64-unknown-linux-musl.tar.gz checksums.txt | head -1 | awk '{print $1}')" >> $GITHUB_OUTPUT
- name: Generate formula
run: |
cat > rtk.rb << 'FORMULA'
class Rtk < Formula
desc "Rust Token Killer - High-performance CLI proxy to minimize LLM token consumption"
homepage "https://www.rtk-ai.app"
version "VERSION_PLACEHOLDER"
license "Apache 2.0"
if OS.mac? && Hardware::CPU.arm?
url "https://github.com/rtk-ai/rtk/releases/download/TAG_PLACEHOLDER/rtk-aarch64-apple-darwin.tar.gz"
sha256 "SHA_MAC_ARM_PLACEHOLDER"
elsif OS.mac? && Hardware::CPU.intel?
url "https://github.com/rtk-ai/rtk/releases/download/TAG_PLACEHOLDER/rtk-x86_64-apple-darwin.tar.gz"
sha256 "SHA_MAC_INTEL_PLACEHOLDER"
elsif OS.linux? && Hardware::CPU.arm?
url "https://github.com/rtk-ai/rtk/releases/download/TAG_PLACEHOLDER/rtk-aarch64-unknown-linux-gnu.tar.gz"
sha256 "SHA_LINUX_ARM_PLACEHOLDER"
elsif OS.linux? && Hardware::CPU.intel?
url "https://github.com/rtk-ai/rtk/releases/download/TAG_PLACEHOLDER/rtk-x86_64-unknown-linux-musl.tar.gz"
sha256 "SHA_LINUX_INTEL_PLACEHOLDER"
end
def install
bin.install "rtk"
end
def caveats
<<~EOS
rtk is installed! Get started:
# Initialize for Claude Code
rtk init -g # Global hook-first setup (recommended)
rtk init # Add to ./CLAUDE.md (this project only)
# See all commands
rtk --help
# Measure your token savings
rtk gain
Full documentation: https://www.rtk-ai.app
EOS
end
test do
system "#{bin}/rtk", "--version"
end
end
FORMULA
sed -i "s/VERSION_PLACEHOLDER/${{ steps.version.outputs.version }}/g" rtk.rb
sed -i "s/TAG_PLACEHOLDER/${{ steps.version.outputs.tag }}/g" rtk.rb
sed -i "s/SHA_MAC_ARM_PLACEHOLDER/${{ steps.sha.outputs.mac_arm }}/g" rtk.rb
sed -i "s/SHA_MAC_INTEL_PLACEHOLDER/${{ steps.sha.outputs.mac_intel }}/g" rtk.rb
sed -i "s/SHA_LINUX_ARM_PLACEHOLDER/${{ steps.sha.outputs.linux_arm }}/g" rtk.rb
sed -i "s/SHA_LINUX_INTEL_PLACEHOLDER/${{ steps.sha.outputs.linux_intel }}/g" rtk.rb
# Remove leading spaces from heredoc
sed -i 's/^ //' rtk.rb
- name: Push to homebrew-tap
run: |
CONTENT=$(base64 -w 0 rtk.rb)
SHA=$(gh api repos/rtk-ai/homebrew-tap/contents/Formula/rtk.rb --jq '.sha' 2>/dev/null || echo "")
if [ -n "$SHA" ]; then
gh api -X PUT repos/rtk-ai/homebrew-tap/contents/Formula/rtk.rb \
-f message="rtk ${{ steps.version.outputs.version }}" \
-f content="$CONTENT" \
-f sha="$SHA"
else
gh api -X PUT repos/rtk-ai/homebrew-tap/contents/Formula/rtk.rb \
-f message="rtk ${{ steps.version.outputs.version }}" \
-f content="$CONTENT"
fi
env:
GH_TOKEN: ${{ secrets.HOMEBREW_TAP_TOKEN }}