diff --git a/voltage/images/buzz-agent/Dockerfile b/voltage/images/buzz-agent/Dockerfile index 267ef6429c1..4cd82225448 100644 --- a/voltage/images/buzz-agent/Dockerfile +++ b/voltage/images/buzz-agent/Dockerfile @@ -16,6 +16,16 @@ RUN cargo build --locked --profile sprig -p sprig \ && strip target/sprig/sprig FROM rust:1.97.1-slim-trixie@sha256:8e8cf8f7fd54a2d23d5a743b3a03f56e26b6c774276c33fa0595111704ebb15c AS development-rust +# cargo-llvm-cov (the engineering-skills rust-crap-score skill) reads coverage +# with the toolchain's LLVM tools. +RUN rustup component add llvm-tools-preview + +# knots 1.16.0, the complexity analyzer rust-crap-score requires. Its releases +# have no linux arm64 binary, so build it from crates.io with its published +# Cargo.lock; cargo checks the crate against the registry checksum. +FROM development-rust AS crap-tools +RUN cargo install --locked --version 1.16.0 knots --root /opt/crap-tools \ + && strip /opt/crap-tools/bin/knots FROM node:22-trixie-slim@sha256:154ba2f4d6fec323d28e4f4bb86bba4677f1223391a1979cf521304e03a98dfa LABEL maintainer="Voltage " @@ -34,20 +44,22 @@ ENV RUSTUP_HOME=/opt/rustup CARGO_HOME=/home/agent/.cargo CARGO_BUILD_JOBS=2 # credentials enter only at runtime. ARG TARGETARCH RUN case "$TARGETARCH" in \ - amd64) via=x86_64 gitleaks=x64 sccache=x86_64 \ + amd64) via=x86_64 gitleaks=x64 sccache=x86_64 llvm_cov=x86_64 \ kubectl_sha=874d5e72dbb819f43cff16bcd1e4f8bac5b7f2361fe1e55049b0a6c676fb0cbf \ via_sha=9e5a7f9384cacda29061d814e9397e7b6d90a72ee4663775362978ac9b65d8b7 \ op_sha=198b05dcf9a0972778ce5a4e262c459979b0c837257b5da65e2fba6187734226 \ gitleaks_sha=551f6fc83ea457d62a0d98237cbad105af8d557003051f41f3e7ca7b3f2470eb \ depot_sha=7a469c767bd3b7e67b3174f91af9c6315fea0ced4871948ee9d41a006fb2347e \ - sccache_sha=45f1447fbe231e3037bde351ef70677dd212216c8d62ae7ca409fecc4d6acc89 ;; \ - arm64) via=arm64 gitleaks=arm64 sccache=aarch64 \ + sccache_sha=45f1447fbe231e3037bde351ef70677dd212216c8d62ae7ca409fecc4d6acc89 \ + llvm_cov_sha=e9fc23dbbdc502ee9267544f8c6e8e162c6d7df8fb2e3f62d8289a4c0bd240ce ;; \ + arm64) via=arm64 gitleaks=arm64 sccache=aarch64 llvm_cov=aarch64 \ kubectl_sha=cc749967b62f4422260bc9c0aa7a7c55f45175ae38cb8d95767b5d2b7e04c1fd \ via_sha=26cc174802c875424b9846a398d4b2d890b911506f7c574dee96859620caa535 \ op_sha=7b5c361a23e738a3969cf676960f4843af4aa9aa68dbec23d18a9134da428ba3 \ gitleaks_sha=e4a487ee7ccd7d3a7f7ec08657610aa3606637dab924210b3aee62570fb4b080 \ depot_sha=042d846f544919e6b5bcb328387b1963af4e298bfa441a41f36bc46663202f2b \ - sccache_sha=2b3284d5da3b46a47dc4229e75bb7b88ac4aa99c8d754fb7d2f84997e5a4354a ;; \ + sccache_sha=2b3284d5da3b46a47dc4229e75bb7b88ac4aa99c8d754fb7d2f84997e5a4354a \ + llvm_cov_sha=bc14a4551e633b12ec89bb15469e6099b4ddc998a5d1bcad0fd95320b148a40f ;; \ *) echo "Unsupported architecture: $TARGETARCH" >&2; exit 1 ;; \ esac \ && curl -fsSL "https://github.com/tee8z/via/releases/download/v0.6.1/via-linux-$via.tar.gz" -o /tmp/via.tar.gz \ @@ -70,7 +82,10 @@ RUN case "$TARGETARCH" in \ && curl -fsSL "https://dl.k8s.io/release/v1.35.8/bin/linux/${TARGETARCH}/kubectl" -o /usr/local/bin/kubectl \ && echo "$kubectl_sha /usr/local/bin/kubectl" | sha256sum -c - \ && chmod 0755 /usr/local/bin/kubectl \ - && rm /tmp/via.tar.gz /tmp/op.zip /tmp/gitleaks.tar.gz /tmp/depot.tar.gz /tmp/sccache.tar.gz + && curl -fsSL "https://github.com/taiki-e/cargo-llvm-cov/releases/download/v0.8.5/cargo-llvm-cov-$llvm_cov-unknown-linux-musl.tar.gz" -o /tmp/cargo-llvm-cov.tar.gz \ + && echo "$llvm_cov_sha /tmp/cargo-llvm-cov.tar.gz" | sha256sum -c - \ + && tar -xzf /tmp/cargo-llvm-cov.tar.gz -C /usr/local/bin cargo-llvm-cov \ + && rm /tmp/via.tar.gz /tmp/op.zip /tmp/gitleaks.tar.gz /tmp/depot.tar.gz /tmp/sccache.tar.gz /tmp/cargo-llvm-cov.tar.gz COPY voltage/images/buzz-agent/checkpoint-gitleaks.toml /opt/agent-tools/checkpoint-gitleaks.toml WORKDIR /opt/buzz-runtimes COPY voltage/images/buzz-agent/package.json voltage/images/buzz-agent/package-lock.json ./ @@ -79,6 +94,7 @@ RUN npm ci --omit=dev --no-audit --no-fund \ && rm -rf node_modules/@anthropic-ai/*-linux-*-musl \ && npm cache clean --force COPY --from=builder /build/target/sprig/sprig /usr/local/bin/sprig +COPY --from=crap-tools /opt/crap-tools/bin/knots /usr/local/bin/knots COPY --chmod=0755 voltage/images/buzz-agent/account.py /usr/local/bin/buzz-agent-account COPY --chmod=0755 voltage/images/buzz-agent/profile.py /usr/local/bin/buzz-agent-profile COPY --chmod=0755 voltage/images/buzz-agent/cache.py /usr/local/bin/buzz-agent-cache diff --git a/voltage/images/buzz-agent/README.md b/voltage/images/buzz-agent/README.md index e6ae73c858c..56abd79c4ff 100644 --- a/voltage/images/buzz-agent/README.md +++ b/voltage/images/buzz-agent/README.md @@ -6,6 +6,7 @@ Staging agent workers are Graviton (`arm64`); the tool downloads in the Dockerfi Each developer supplies their own accounts at runtime. The image contains Codex and Claude ACP adapters, Rust, Node, Go, Python, Git, GitHub CLI, `via`, `kubectl`, and SQL clients. +For the `rust-crap-score` engineering skill, it also has `cargo-llvm-cov` 0.8.5 (checksum-pinned release), `knots` 1.16.0 (built from crates.io with its published lockfile), and the toolchain's `llvm-tools-preview` component. The skill rejects other versions. Buzz selects the runtime, model, persona, and arguments for each session. The image builds the Buzz source of the commit it is built from; npm dependencies use the committed lockfile. diff --git a/voltage/images/buzz-agent/test-image.sh b/voltage/images/buzz-agent/test-image.sh index 595e3cedb4b..22e6d64c31d 100755 --- a/voltage/images/buzz-agent/test-image.sh +++ b/voltage/images/buzz-agent/test-image.sh @@ -51,5 +51,21 @@ docker run --rm --tmpfs /home/agent:uid=10001,gid=10001,mode=0700 \ claude --version >/dev/null command -v codex-acp claude-agent-acp buzz-dev-mcp >/dev/null ' +# rust-crap-score accepts exactly these versions. Collect real coverage and +# complexity for a one-function crate, offline, as the skill does. +docker run --rm --entrypoint /bin/bash "$IMAGE" -ceu ' + test "$(cargo llvm-cov --version)" = "cargo-llvm-cov 0.8.5" + test "$(knots --version)" = "knots 1.16.0" + rustup component list --installed | grep -q "^llvm-tools" + cd "$(mktemp -d)" + cargo new --quiet --vcs none --lib crap-smoke + cd crap-smoke + printf "%s\n" "pub fn magnitude(x: i32) -> i32 { if x > 0 { x } else { -x } }" \ + "#[test] fn negative() { assert_eq!(magnitude(-1), 1); }" > src/lib.rs + cargo llvm-cov --offline --lcov --output-path lcov.info 2>/dev/null + grep -q "^SF:.*src/lib.rs" lcov.info + knots --recursive --language rust --count-anonymous-closures --format json src \ + | python3 -c "import json, sys; json.load(sys.stdin)" +' python3 "$(dirname "$0")/test-review.py" "$IMAGE" printf 'PASS: agent adapter packaging and unauthenticated ACP initialization (%s)\n' "$IMAGE"