Skip to content

fix(skillfs): preserve SLS logs on panic #3883

fix(skillfs): preserve SLS logs on panic

fix(skillfs): preserve SLS logs on panic #3883

Workflow file for this run

name: CI
on:
push:
branches: [main, 'release/**']
pull_request:
branches: [main, 'release/**']
workflow_dispatch:
inputs:
run_copilot_shell:
description: 'Force run copilot-shell tests (ignore change detection)'
required: false
type: boolean
default: false
run_agent_sec:
description: 'Force run agent-sec-core tests (ignore change detection)'
required: false
type: boolean
default: false
run_agentsight:
description: 'Force run agentsight tests (ignore change detection)'
required: false
type: boolean
default: false
run_tokenless:
description: 'Force run tokenless tests (ignore change detection)'
required: false
type: boolean
default: false
run_ws_ckpt:
description: 'Force run ws-ckpt tests (ignore change detection)'
required: false
type: boolean
default: false
run_agent_memory:
description: 'Force run agent-memory tests (ignore change detection)'
required: false
type: boolean
default: false
run_anolisa:
description: 'Force run anolisa tests (ignore change detection)'
required: false
type: boolean
default: false
run_ktuner:
description: 'Force run ktuner tests (ignore change detection)'
required: false
type: boolean
default: false
run_anvil:
description: 'Force run anvil tests (ignore change detection)'
required: false
type: boolean
default: false
run_skillfs:
description: 'Force run SkillFS tests (ignore change detection)'
required: false
type: boolean
default: false
run_cosh_ng:
description: 'Force run cosh-ng tests (ignore change detection)'
required: false
type: boolean
default: false
permissions:
contents: read
env:
RUSTUP_DIST_SERVER: https://static.rust-lang.org
RUSTUP_UPDATE_ROOT: https://static.rust-lang.org/rustup
jobs:
# =========================================================================
# Step 1: Detect which components have changed
# =========================================================================
detect-changes:
name: Detect Changes
runs-on: anolisa-k8s-general-ci-x64
outputs:
copilot_shell: ${{ steps.changes.outputs.copilot_shell }}
agent_sec_core: ${{ steps.changes.outputs.agent_sec_core }}
agentsight: ${{ steps.changes.outputs.agentsight }}
tokenless: ${{ steps.changes.outputs.tokenless }}
ws_ckpt: ${{ steps.changes.outputs.ws_ckpt }}
agent_memory: ${{ steps.changes.outputs.agent_memory }}
anolisa: ${{ steps.changes.outputs.anolisa }}
skillfs: ${{ steps.changes.outputs.skillfs }}
cosh_ng: ${{ steps.changes.outputs.cosh_ng }}
ktuner: ${{ steps.changes.outputs.ktuner }}
anvil: ${{ steps.changes.outputs.anvil }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 2
- name: Check which components changed
id: changes
run: |
set -e
echo "=== Detect Changes ==="
# Get changed files
if git rev-parse HEAD~1 >/dev/null 2>&1; then
CHANGED=$(git diff --name-only HEAD~1 HEAD || true)
else
CHANGED=$(git diff --name-only --diff-filter=ACMRT HEAD || true)
fi
echo "Changed files:"
echo "$CHANGED"
COPILOT_SHELL=false
AGENT_SEC=false
AGENTSIGHT=false
TOKENLESS=false
WS_CKPT=false
AGENT_MEMORY=false
ANOLISA=false
SKILLFS=false
COSH_NG=false
KTUNER=false
ANVIL=false
# Path-based detection
if echo "$CHANGED" | grep -q "^src/copilot-shell/"; then
COPILOT_SHELL=true
fi
if echo "$CHANGED" | grep -q "^src/agent-sec-core/"; then
AGENT_SEC=true
fi
if echo "$CHANGED" | grep -q "^src/agentsight/"; then
AGENTSIGHT=true
fi
if echo "$CHANGED" | grep -q "^src/tokenless/"; then
TOKENLESS=true
fi
if echo "$CHANGED" | grep -q "^src/ws-ckpt/"; then
WS_CKPT=true
fi
if echo "$CHANGED" | grep -q "^src/agent-memory/"; then
AGENT_MEMORY=true
fi
if echo "$CHANGED" | grep -q "^src/anolisa/"; then
ANOLISA=true
fi
if echo "$CHANGED" | grep -q "^src/skillfs/"; then
SKILLFS=true
fi
if echo "$CHANGED" | grep -q "^src/cosh-ng/"; then
COSH_NG=true
fi
if echo "$CHANGED" | grep -q "^src/ktuner/"; then
KTUNER=true
fi
if echo "$CHANGED" | grep -q "^src/anvil/"; then
ANVIL=true
fi
# Manual override via workflow_dispatch
if [[ "${{ inputs.run_copilot_shell }}" == "true" ]]; then
COPILOT_SHELL=true
fi
if [[ "${{ inputs.run_agent_sec }}" == "true" ]]; then
AGENT_SEC=true
fi
if [[ "${{ inputs.run_agentsight }}" == "true" ]]; then
AGENTSIGHT=true
fi
if [[ "${{ inputs.run_tokenless }}" == "true" ]]; then
TOKENLESS=true
fi
if [[ "${{ inputs.run_ws_ckpt }}" == "true" ]]; then
WS_CKPT=true
fi
if [[ "${{ inputs.run_agent_memory }}" == "true" ]]; then
AGENT_MEMORY=true
fi
if [[ "${{ inputs.run_anolisa }}" == "true" ]]; then
ANOLISA=true
fi
if [[ "${{ inputs.run_skillfs }}" == "true" ]]; then
SKILLFS=true
fi
if [[ "${{ inputs.run_cosh_ng }}" == "true" ]]; then
COSH_NG=true
fi
if [[ "${{ inputs.run_ktuner }}" == "true" ]]; then
KTUNER=true
fi
if [[ "${{ inputs.run_anvil }}" == "true" ]]; then
ANVIL=true
fi
echo "copilot_shell=$COPILOT_SHELL" >> $GITHUB_OUTPUT
echo "agent_sec_core=$AGENT_SEC" >> $GITHUB_OUTPUT
echo "agentsight=$AGENTSIGHT" >> $GITHUB_OUTPUT
echo "tokenless=$TOKENLESS" >> $GITHUB_OUTPUT
echo "ws_ckpt=$WS_CKPT" >> $GITHUB_OUTPUT
echo "agent_memory=$AGENT_MEMORY" >> $GITHUB_OUTPUT
echo "anolisa=$ANOLISA" >> $GITHUB_OUTPUT
echo "skillfs=$SKILLFS" >> $GITHUB_OUTPUT
echo "cosh_ng=$COSH_NG" >> $GITHUB_OUTPUT
echo "ktuner=$KTUNER" >> $GITHUB_OUTPUT
echo "anvil=$ANVIL" >> $GITHUB_OUTPUT
echo "### Change Detection Results" >> $GITHUB_STEP_SUMMARY
echo "| Component | Changed |" >> $GITHUB_STEP_SUMMARY
echo "|-----------|---------|" >> $GITHUB_STEP_SUMMARY
echo "| copilot-shell | $COPILOT_SHELL |" >> $GITHUB_STEP_SUMMARY
echo "| agent-sec-core | $AGENT_SEC |" >> $GITHUB_STEP_SUMMARY
echo "| agentsight | $AGENTSIGHT |" >> $GITHUB_STEP_SUMMARY
echo "| tokenless | $TOKENLESS |" >> $GITHUB_STEP_SUMMARY
echo "| ws-ckpt | $WS_CKPT |" >> $GITHUB_STEP_SUMMARY
echo "| agent-memory | $AGENT_MEMORY |" >> $GITHUB_STEP_SUMMARY
echo "| anolisa | $ANOLISA |" >> $GITHUB_STEP_SUMMARY
echo "| SkillFS | $SKILLFS |" >> $GITHUB_STEP_SUMMARY
echo "| cosh-ng | $COSH_NG |" >> $GITHUB_STEP_SUMMARY
echo "| ktuner | $KTUNER |" >> $GITHUB_STEP_SUMMARY
echo "| anvil | $ANVIL |" >> $GITHUB_STEP_SUMMARY
# =========================================================================
# Step 2: Build & Lint copilot-shell
# =========================================================================
build-copilot-shell:
name: Build copilot-shell
needs: detect-changes
if: needs.detect-changes.outputs.copilot_shell == 'true'
runs-on: anolisa-k8s-general-ci-x64
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
cache-dependency-path: src/copilot-shell/package-lock.json
- name: Install dependencies
run: cd src/copilot-shell && npm ci
- name: Check formatting
run: |
cd src/copilot-shell
npm run format
git diff --exit-code
- name: Lint
run: cd src/copilot-shell && npm run lint:ci
- name: Build
run: cd src/copilot-shell && npm run build
- name: Type check
run: cd src/copilot-shell && npm run typecheck
- name: Upload build artifacts
uses: actions/upload-artifact@v4
with:
name: build-dist-nodejs20
path: |
src/copilot-shell/packages/*/dist/**/*
src/copilot-shell/package-lock.json
retention-days: 1
# =========================================================================
# Step 3: Test copilot-shell (cli + core in parallel)
# =========================================================================
test-copilot-shell-cli:
name: Test copilot-shell/cli
needs: [detect-changes, build-copilot-shell]
if: needs.detect-changes.outputs.copilot_shell == 'true'
runs-on: anolisa-k8s-general-ci-x64
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
cache-dependency-path: src/copilot-shell/package-lock.json
- uses: actions/download-artifact@v4
with:
name: build-dist-nodejs20
path: '.'
- name: Install and build
run: |
cd src/copilot-shell
npm ci
npm run build
- name: Run cli tests
run: |
cd src/copilot-shell
NO_COLOR=true npm run test:ci --workspace=@copilot-shell/cli
test-copilot-shell-core:
name: Test copilot-shell/core
needs: [detect-changes, build-copilot-shell]
if: needs.detect-changes.outputs.copilot_shell == 'true'
runs-on: anolisa-k8s-general-ci-x64
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
cache-dependency-path: src/copilot-shell/package-lock.json
- uses: actions/download-artifact@v4
with:
name: build-dist-nodejs20
path: '.'
- name: Install and build
run: |
cd src/copilot-shell
npm ci
npm run build
- name: Run core tests
run: |
cd src/copilot-shell
NO_COLOR=true npm run test:ci --workspace=@copilot-shell/core
# =========================================================================
# Step 4: Test agent-sec-core (Linux only)
# =========================================================================
test-agent-sec-core:
name: Test agent-sec-core
needs: detect-changes
if: needs.detect-changes.outputs.agent_sec_core == 'true'
runs-on: anolisa-k8s-general-ci-x64
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: dtolnay/rust-toolchain@stable
with:
toolchain: '1.93.0'
components: llvm-tools-preview
- name: Install cargo-llvm-cov
uses: taiki-e/install-action@cargo-llvm-cov
- name: astral-sh/setup-uv
uses: astral-sh/setup-uv@v8.0.0
with:
python-version: '3.11.6'
- uses: Swatinem/rust-cache@v2
with:
workspaces: src/agent-sec-core/linux-sandbox
- name: Install system dependencies
run: |
sudo apt-get update
sudo apt-get install -y clang llvm libssl-dev libseccomp-dev bubblewrap
- name: Install diff-cover
run: uv tool install --force diff-cover
- name: Check formatting
run: |
cd src/agent-sec-core
make python-code-pretty
if ! git diff-index --quiet HEAD --; then
echo "ERROR: Code style check failed. Please run 'make python-code-pretty' locally." >&2
git status --porcelain >&2
exit 1
fi
echo "Code style check passed."
- name: Lint check (incremental)
if: github.event_name == 'pull_request'
run: |
cd src/agent-sec-core
uv run --project agent-sec-cli ruff check --config agent-sec-cli/pyproject.toml --output-format=concise . > ruff_report.txt || true
# Prefix paths to match git-diff repo-root-relative paths
sed -i 's|^\([^: ]*\.py\)|src/agent-sec-core/\1|' ruff_report.txt
cd "$GITHUB_WORKSPACE"
LINT_OUTPUT=$(diff-quality --violations=flake8 --fail-under=100 src/agent-sec-core/ruff_report.txt 2>&1) || true
rm -f src/agent-sec-core/ruff_report.txt
echo "$LINT_OUTPUT"
# Extract violation count from diff-quality output
if echo "$LINT_OUTPUT" | grep -q "Failure"; then
{
echo "### ⚠️ agent-sec-core Lint Warnings (incremental)"
echo ""
echo '以下为 PR 变更行中的 ruff lint 违规(不卡点,仅提示):'
echo ""
echo '```'
echo "$LINT_OUTPUT"
echo '```'
} >> "$GITHUB_STEP_SUMMARY"
echo "::warning::Lint violations found in changed lines. See step summary for details."
else
echo "### ✅ agent-sec-core Lint Check Passed" >> "$GITHUB_STEP_SUMMARY"
fi
- name: Run Python tests with coverage
run: |
cd src/agent-sec-core
make test-python-coverage
- name: Generate coverage summary
if: always()
shell: bash
run: |
cd src/agent-sec-core
if [ -f coverage.xml ]; then
LINE_RATE=$(grep -oP 'line-rate="\K[^"]+' coverage.xml | head -1)
BRANCH_RATE=$(grep -oP 'branch-rate="\K[^"]+' coverage.xml | head -1)
LINES_VALID=$(grep -oP 'lines-valid="\K[^"]+' coverage.xml | head -1)
LINES_COVERED=$(grep -oP 'lines-covered="\K[^"]+' coverage.xml | head -1)
BRANCHES_VALID=$(grep -oP 'branches-valid="\K[^"]+' coverage.xml | head -1)
BRANCHES_COVERED=$(grep -oP 'branches-covered="\K[^"]+' coverage.xml | head -1)
LINE_PCT=$(echo "$LINE_RATE * 100" | bc -l | xargs printf '%.1f')
BRANCH_PCT=$(echo "$BRANCH_RATE * 100" | bc -l | xargs printf '%.1f')
{
echo "### 🧪 agent-sec-core (Python) Test Coverage"
echo ""
echo "| Metric | Coverage | Detail |"
echo "|--------|----------|--------|"
echo "| 🟢 Line Coverage | **${LINE_PCT}%** | ${LINES_COVERED}/${LINES_VALID} lines |"
echo "| 🟢 Branch Coverage | **${BRANCH_PCT}%** | ${BRANCHES_COVERED}/${BRANCHES_VALID} branches |"
} >> "$GITHUB_STEP_SUMMARY"
else
echo "### ⚠️ Coverage report not found" >> "$GITHUB_STEP_SUMMARY"
fi
- name: Check uv.lock in sync with pyproject.toml
run: |
cd src/agent-sec-core/agent-sec-cli
uv lock --check
- name: Check requirements.txt in sync with uv.lock
run: |
cd src/agent-sec-core
make export-requirements
if ! git diff --quiet -- agent-sec-cli/requirements.txt; then
echo "ERROR: agent-sec-cli/requirements.txt is out of sync with uv.lock." >&2
echo "Please run 'make export-requirements' locally and commit the result." >&2
git diff -- agent-sec-cli/requirements.txt >&2
exit 1
fi
echo "requirements.txt is in sync with uv.lock."
- name: Run Rust tests with coverage
run: |
cd src/agent-sec-core
make test-rust-coverage
- name: Generate Rust coverage summary
if: always()
shell: bash
run: |
COV_FILE="src/agent-sec-core/rust-coverage.xml"
if [ -f "$COV_FILE" ]; then
LINE_RATE=$(grep -oP 'line-rate="\K[^"]+' "$COV_FILE" | head -1)
LINES_VALID=$(grep -oP 'lines-valid="\K[^"]+' "$COV_FILE" | head -1)
LINES_COVERED=$(grep -oP 'lines-covered="\K[^"]+' "$COV_FILE" | head -1)
LINE_PCT=$(echo "$LINE_RATE * 100" | bc -l | xargs printf '%.1f')
{
echo "### 🧪 agent-sec-core/linux-sandbox (Rust) Test Coverage"
echo ""
echo "| Metric | Coverage | Detail |"
echo "|--------|----------|--------|"
echo "| 🟢 Line Coverage | **${LINE_PCT}%** | ${LINES_COVERED}/${LINES_VALID} lines |"
echo ""
echo "> ℹ️ cargo-llvm-cov 不支持分支覆盖统计,仅展示行覆盖率。"
} >> "$GITHUB_STEP_SUMMARY"
else
echo "### ⚠️ linux-sandbox Rust coverage report not found" >> "$GITHUB_STEP_SUMMARY"
fi
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Run openclaw-plugin tests with coverage
run: |
cd src/agent-sec-core
cd openclaw-plugin && npm install
cd ..
make test-openclaw-plugin-coverage
- name: Generate openclaw-plugin coverage summary
if: always()
shell: bash
run: |
COV_FILE="src/agent-sec-core/openclaw-plugin/coverage/cobertura-coverage.xml"
if [ -f "$COV_FILE" ]; then
LINE_RATE=$(grep -oP 'line-rate="\K[^"]+' "$COV_FILE" | head -1)
BRANCH_RATE=$(grep -oP 'branch-rate="\K[^"]+' "$COV_FILE" | head -1)
LINES_VALID=$(grep -oP 'lines-valid="\K[^"]+' "$COV_FILE" | head -1)
LINES_COVERED=$(grep -oP 'lines-covered="\K[^"]+' "$COV_FILE" | head -1)
BRANCHES_VALID=$(grep -oP 'branches-valid="\K[^"]+' "$COV_FILE" | head -1)
BRANCHES_COVERED=$(grep -oP 'branches-covered="\K[^"]+' "$COV_FILE" | head -1)
LINE_PCT=$(echo "$LINE_RATE * 100" | bc -l | xargs printf '%.1f')
BRANCH_PCT=$(echo "$BRANCH_RATE * 100" | bc -l | xargs printf '%.1f')
{
echo "### 🧪 agent-sec-core/openclaw-plugin (TypeScript) Test Coverage"
echo ""
echo "| Metric | Coverage | Detail |"
echo "|--------|----------|--------|"
echo "| 🟢 Line Coverage | **${LINE_PCT}%** | ${LINES_COVERED}/${LINES_VALID} lines |"
echo "| 🟢 Branch Coverage | **${BRANCH_PCT}%** | ${BRANCHES_COVERED}/${BRANCHES_VALID} branches |"
} >> "$GITHUB_STEP_SUMMARY"
else
echo "### ⚠️ openclaw-plugin coverage report not found" >> "$GITHUB_STEP_SUMMARY"
fi
- name: Incremental coverage gate (PR only)
if: github.event_name == 'pull_request'
shell: bash
run: |
cd src/agent-sec-core
COMPARE_BRANCH="origin/${{ github.base_ref }}"
echo "=== Diff-cover: comparing against $COMPARE_BRANCH ==="
DIFF_COV_SUMMARY=""
GATE_FAILED=false
THRESHOLD=80
# Helper: extract coverage stats from diff-cover output
extract_diff_cov_stats() {
local output="$1"
local pct missing total covered
pct=$(echo "$output" | grep -oP 'Coverage:\s*\K[0-9.]+(?=%)' || echo "")
total=$(echo "$output" | grep -oP 'Total:\s+\K[0-9]+' || echo "")
missing=$(echo "$output" | grep -oP 'Missing:\s*\K[0-9]+' || echo "")
if [ -n "$total" ] && [ -n "$missing" ]; then
covered=$((total - missing))
echo "${pct}|${covered}/${total} lines"
elif [ -n "$pct" ]; then
echo "${pct}|-"
else
echo "N/A|-"
fi
}
# --- Python ---
if [ -f coverage.xml ]; then
echo "--- Python incremental coverage ---"
PYTHON_OUTPUT=$(diff-cover coverage.xml --compare-branch="$COMPARE_BRANCH" --fail-under=$THRESHOLD 2>&1) || GATE_FAILED=true
echo "$PYTHON_OUTPUT"
PYTHON_STATS=$(extract_diff_cov_stats "$PYTHON_OUTPUT")
PYTHON_PCT=$(echo "$PYTHON_STATS" | cut -d'|' -f1)
PYTHON_DETAIL=$(echo "$PYTHON_STATS" | cut -d'|' -f2)
DIFF_COV_SUMMARY="${DIFF_COV_SUMMARY}| Python | ${PYTHON_PCT}% | ${PYTHON_DETAIL} | ${THRESHOLD}% |\n"
fi
# --- Rust ---
if [ -f rust-coverage.xml ]; then
echo "--- Rust incremental coverage ---"
RUST_OUTPUT=$(diff-cover rust-coverage.xml --compare-branch="$COMPARE_BRANCH" --fail-under=$THRESHOLD 2>&1) || GATE_FAILED=true
echo "$RUST_OUTPUT"
RUST_STATS=$(extract_diff_cov_stats "$RUST_OUTPUT")
RUST_PCT=$(echo "$RUST_STATS" | cut -d'|' -f1)
RUST_DETAIL=$(echo "$RUST_STATS" | cut -d'|' -f2)
DIFF_COV_SUMMARY="${DIFF_COV_SUMMARY}| Rust | ${RUST_PCT}% | ${RUST_DETAIL} | ${THRESHOLD}% |\n"
fi
# --- TypeScript ---
TS_COV="openclaw-plugin/coverage/cobertura-coverage.xml"
if [ -f "$TS_COV" ]; then
echo "--- TypeScript incremental coverage ---"
TS_OUTPUT=$(diff-cover "$TS_COV" --compare-branch="$COMPARE_BRANCH" --fail-under=$THRESHOLD 2>&1) || GATE_FAILED=true
echo "$TS_OUTPUT"
TS_STATS=$(extract_diff_cov_stats "$TS_OUTPUT")
TS_PCT=$(echo "$TS_STATS" | cut -d'|' -f1)
TS_DETAIL=$(echo "$TS_STATS" | cut -d'|' -f2)
DIFF_COV_SUMMARY="${DIFF_COV_SUMMARY}| TypeScript | ${TS_PCT}% | ${TS_DETAIL} | ${THRESHOLD}% |\n"
fi
# --- Summary ---
{
echo "### 🚦 Incremental Coverage Gate (new/changed code)"
echo ""
echo "| Language | New Code Coverage | Detail | Threshold |"
echo "|----------|-------------------|--------|-----------|"
echo -e "$DIFF_COV_SUMMARY"
if [ "$GATE_FAILED" = true ]; then
echo ""
echo "> ❌ **Gate FAILED**: New code coverage is below ${THRESHOLD}%. Please add tests for uncovered changes."
else
echo ""
echo "> ✅ **Gate PASSED**: All new code meets the ${THRESHOLD}% coverage threshold."
fi
} >> "$GITHUB_STEP_SUMMARY"
if [ "$GATE_FAILED" = true ]; then
echo "::error::Incremental coverage gate failed: new code coverage < ${THRESHOLD}%"
exit 1
fi
# =========================================================================
# Step 5: Test agentsight (Linux only)
# =========================================================================
test-agentsight:
name: Test agentsight
needs: detect-changes
if: needs.detect-changes.outputs.agentsight == 'true'
runs-on: anolisa-k8s-general-ci-x64
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: dtolnay/rust-toolchain@stable
with:
toolchain: '1.89.0'
components: 'rustfmt, clippy, llvm-tools-preview'
- name: Install cargo-llvm-cov
uses: taiki-e/install-action@cargo-llvm-cov
- uses: Swatinem/rust-cache@v2
with:
workspaces: src/agentsight
- name: astral-sh/setup-uv
uses: astral-sh/setup-uv@v8.0.0
with:
python-version: '3.11.6'
- name: Install eBPF build dependencies
run: |
sudo apt-get update
sudo apt-get install -y clang llvm libbpf-dev libelf-dev elfutils libssl-dev zlib1g-dev pkg-config
sudo apt-get install -y perl
- name: Check formatting
run: |
cd src/agentsight
cargo fmt --all --check
- name: Check architecture boundaries
run: |
cd src/agentsight
python3 scripts/check-arch-boundaries.py
- name: Lint
run: |
cd src/agentsight
cargo clippy --all-targets -- -D warnings
- name: Run tests with coverage
run: |
cd src/agentsight
cargo llvm-cov --cobertura --output-path coverage.xml \
--ignore-filename-regex '(\.skel\.rs|target/debug/build|target/release/build|src/probes/)'
- name: Generate coverage summary
if: always()
shell: python3 {0}
run: |
import xml.etree.ElementTree as ET, os, pathlib
cov = pathlib.Path("src/agentsight/coverage.xml")
summary = os.environ.get("GITHUB_STEP_SUMMARY", "/dev/null")
with open(summary, "a") as f:
if cov.exists():
root = ET.parse(cov).getroot()
rate = float(root.get("line-rate", "0"))
valid = root.get("lines-valid", "0")
covered = root.get("lines-covered", "0")
pct = f"{rate * 100:.1f}"
f.write("### agentsight (Rust) Test Coverage\n\n")
f.write("| Metric | Coverage | Detail |\n")
f.write("|--------|----------|--------|\n")
f.write(f"| Line Coverage | **{pct}%** | {covered}/{valid} lines |\n")
else:
f.write("### agentsight coverage report not found\n")
- name: Upload coverage artifact
if: always()
uses: actions/upload-artifact@v4
with:
name: agentsight-coverage
path: src/agentsight/coverage.xml
retention-days: 30
- name: Install diff-cover
if: github.event_name == 'pull_request'
run: uv tool install --force diff-cover
- name: Incremental coverage gate (PR only)
if: github.event_name == 'pull_request'
env:
COMPARE_BRANCH: "origin/${{ github.base_ref }}"
run: |
cd src/agentsight
THRESHOLD=80
GATE_FAILED=false
echo "=== Diff-cover: comparing against $COMPARE_BRANCH ==="
DIFF_OUTPUT=$(diff-cover coverage.xml --compare-branch="$COMPARE_BRANCH" --fail-under=$THRESHOLD 2>&1) || GATE_FAILED=true
echo "$DIFF_OUTPUT"
PCT=$(echo "$DIFF_OUTPUT" | grep -oP 'Coverage:\s*\K[0-9.]+(?=%)' || echo "N/A")
TOTAL=$(echo "$DIFF_OUTPUT" | grep -oP 'Total:\s+\K[0-9]+' || echo "-")
MISSING=$(echo "$DIFF_OUTPUT" | grep -oP 'Missing:\s*\K[0-9]+' || echo "-")
{
echo "### Incremental Coverage Gate (agentsight)"
echo ""
echo "| New Code Coverage | Total New Lines | Missing | Threshold |"
echo "|-------------------|-----------------|---------|-----------|"
echo "| ${PCT}% | ${TOTAL} | ${MISSING} | ${THRESHOLD}% |"
if [ "${GATE_FAILED:-}" = "true" ]; then
echo ""
echo "> **Gate FAILED**: New code coverage is below ${THRESHOLD}%. Please add tests for uncovered changes."
echo ""
echo "<details><summary>Uncovered files (click to expand)</summary>"
echo ""
echo '```'
echo "$DIFF_OUTPUT" | grep -E '^\S.*Missing lines' || true
echo '```'
echo "</details>"
else
echo ""
echo "> **Gate PASSED**: All new code meets the ${THRESHOLD}% coverage threshold."
fi
} >> "$GITHUB_STEP_SUMMARY"
if [ "${GATE_FAILED:-}" = "true" ]; then
echo "::error::Incremental coverage gate failed: new code coverage < ${THRESHOLD}%"
exit 1
fi
# =========================================================================
# Step 6: Test tokenless
# =========================================================================
test-tokenless:
name: Test tokenless
needs: detect-changes
if: needs.detect-changes.outputs.tokenless == 'true'
runs-on: anolisa-k8s-general-ci-x64
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
toolchain: '1.89.0'
components: 'rustfmt, clippy'
- uses: Swatinem/rust-cache@v2
with:
workspaces: src/tokenless
- name: Check component contract
run: make -C src/tokenless check-component-contract
- name: Check formatting
run: |
cd src/tokenless
cargo fmt --all -- --check
- name: Lint
run: |
cd src/tokenless
cargo clippy --workspace -- -D warnings
- name: Run tests
run: |
cd src/tokenless
cargo test --workspace -- --test-threads=1
# =========================================================================
# Step 7: Test ws-ckpt
# =========================================================================
test-ws-ckpt:
name: Test ws-ckpt
needs: detect-changes
if: needs.detect-changes.outputs.ws_ckpt == 'true'
runs-on: anolisa-k8s-general-ci-x64
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
components: 'rustfmt, clippy'
- uses: Swatinem/rust-cache@v2
with:
workspaces: src/ws-ckpt/src
- name: Install system dependencies
run: |
sudo apt-get update
sudo apt-get install -y btrfs-progs rsync
- name: Check formatting
run: |
cd src/ws-ckpt/src
cargo fmt --all --check
- name: Lint
run: |
cd src/ws-ckpt/src
cargo clippy --workspace -- -D warnings
- name: Run tests
run: |
cd src/ws-ckpt/src
cargo test --workspace
# ── Rust coverage gate ──────────────────────────────────────────────
- name: Rust coverage gate (≥45%)
run: |
cargo install cargo-tarpaulin --locked
cd src/ws-ckpt/src
cargo tarpaulin --workspace --out Stdout --fail-under 45
# ── Plugin tests + coverage gates ───────────────────────────────────
- uses: actions/setup-node@v4
with:
node-version: '20'
- name: OpenClaw plugin coverage gate (≥90%)
working-directory: src/ws-ckpt/src/plugins/openclaw
run: |
npm install -g npm@10.9.0
npm ci --registry=https://registry.npmmirror.com --replace-registry-host=always
./node_modules/.bin/vitest run --coverage
- uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Hermes plugin coverage gate (≥90%)
run: |
pip install pytest pytest-cov pyyaml
cd src/ws-ckpt/src/plugins
python -m pytest hermes/tests/ --cov=hermes --cov-report=term --cov-fail-under=90
# =========================================================================
# Step 8: Test agent-memory (Linux-only Rust crate)
# =========================================================================
test-agent-memory:
name: Test agent-memory
needs: detect-changes
if: needs.detect-changes.outputs.agent_memory == 'true'
runs-on: anolisa-k8s-general-ci-x64
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
toolchain: '1.89.0'
components: 'rustfmt, clippy'
- uses: Swatinem/rust-cache@v2
with:
workspaces: src/agent-memory
- name: Install system dependencies
run: |
sudo apt-get update
sudo apt-get install -y libsystemd-dev cmake
- name: Check formatting
run: |
cd src/agent-memory
cargo fmt --all --check
- name: Lint
run: |
cd src/agent-memory
cargo clippy --all-targets --locked -- -D warnings
- name: Run tests
run: |
cd src/agent-memory
cargo test --locked
# =========================================================================
# Step 9: Test anolisa (Rust workspace)
# =========================================================================
test-anolisa:
name: Test anolisa
needs: detect-changes
if: needs.detect-changes.outputs.anolisa == 'true'
runs-on: anolisa-k8s-general-ci-x64
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
toolchain: '1.88.0'
components: 'rustfmt, clippy'
- uses: Swatinem/rust-cache@v2
with:
workspaces: src/anolisa
- name: Check formatting
run: |
cd src/anolisa
cargo fmt --all --check
- name: Lint
run: |
cd src/anolisa
cargo clippy --all-targets --locked -- -D warnings
- name: Run tests
run: |
cd src/anolisa
cargo test --locked
# =========================================================================
# Step 10: Test SkillFS (Rust workspace)
# =========================================================================
test-skillfs:
name: Test SkillFS
needs: detect-changes
if: needs.detect-changes.outputs.skillfs == 'true'
runs-on: anolisa-k8s-general-ci-x64
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
toolchain: '1.86.0'
components: 'rustfmt, clippy'
- uses: Swatinem/rust-cache@v2
with:
workspaces: src/skillfs
- name: Install system dependencies
run: |
sudo apt-get update
sudo apt-get install -y fuse3 libfuse3-dev pkg-config
- name: Check formatting
run: |
cd src/skillfs
cargo fmt --all --check
- name: Lint
run: |
cd src/skillfs
cargo clippy --workspace --all-targets -- -D warnings
- name: Run tests
run: |
cd src/skillfs
cargo test --workspace
- name: Run FUSE smoke test
run: |
cd src/skillfs
scripts/test.sh
# =========================================================================
# Step 11: Test cosh-ng (Rust workspace)
# =========================================================================
test-cosh-ng:
name: Test cosh-ng
needs: detect-changes
if: needs.detect-changes.outputs.cosh_ng == 'true'
runs-on: anolisa-k8s-general-ci-x64
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
components: 'rustfmt, clippy'
- uses: Swatinem/rust-cache@v2
with:
workspaces: src/cosh-ng
- name: Install system dependencies
run: |
sudo apt-get update
sudo apt-get install -y libssl-dev pkg-config
- name: Check formatting
run: |
cd src/cosh-ng
cargo fmt --all --check
- name: Lint
run: |
cd src/cosh-ng
cargo clippy --workspace --all-targets -- -D warnings
- name: Run tests
run: |
cd src/cosh-ng
cargo test --workspace
# =========================================================================
# Step 12: Test ktuner (deterministic kernel-tuning engine)
# =========================================================================
test-ktuner:
name: Test ktuner
needs: detect-changes
if: needs.detect-changes.outputs.ktuner == 'true'
runs-on: anolisa-k8s-general-ci-x64
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
toolchain: '1.89.0'
components: 'rustfmt, clippy'
- uses: Swatinem/rust-cache@v2
with:
workspaces: src/ktuner
- name: Check formatting
run: |
cd src/ktuner
cargo fmt --all --check
- name: Lint
run: |
cd src/ktuner
cargo clippy --all-targets -- -D warnings
- name: Run tests
run: |
cd src/ktuner
cargo test
# =========================================================================
# Step 13: Test anvil (per-host sandbox daemon)
# =========================================================================
test-anvil:
name: Test anvil
needs: detect-changes
if: needs.detect-changes.outputs.anvil == 'true'
runs-on: anolisa-k8s-general-ci-x64
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
toolchain: '1.88.0'
components: 'rustfmt, clippy'
- uses: Swatinem/rust-cache@v2
with:
workspaces: src/anvil
- name: Check formatting
run: |
cd src/anvil
cargo fmt --all --check
- name: Lint
run: |
cd src/anvil
cargo clippy --workspace --all-targets -- -D warnings
- name: Run tests
run: |
cd src/anvil
cargo test --workspace