diff --git a/.github/workflows/image-size.yml b/.github/workflows/image-size.yml new file mode 100644 index 0000000..63cdf3d --- /dev/null +++ b/.github/workflows/image-size.yml @@ -0,0 +1,90 @@ +name: image-size + +# Builds the aidc Dockerfile template with a representative toolchain set and +# reports/asserts the resulting image size so regressions are caught before +# merge. The budget is a soft gate (informational) — the comment is posted on +# PRs so maintainers can see the number without blocking development. + +on: + pull_request: + branches: [main] + push: + branches: [main] + +permissions: + contents: read + pull-requests: write + +concurrency: + group: image-size-${{ github.ref }} + cancel-in-progress: true + +jobs: + size: + name: image size (go,node,python) + runs-on: ubuntu-latest + timeout-minutes: 60 + + steps: + - uses: actions/checkout@v6 + + - name: Scaffold representative project + run: | + set -euo pipefail + mkdir -p work + cp templates/devcontainer/Dockerfile.tmpl work/Dockerfile + cat > work/project-setup.sh <<'EOF' + #!/usr/bin/env bash + set -euo pipefail + : + EOF + # Trigger the go/node/python toolchain arms. + touch work/go.mod + printf '{"name":"x","version":"1.0.0"}\n' > work/package.json + printf 'uv\n' > work/uv.lock + + - name: Build image + run: | + set -euo pipefail + docker build \ + --build-arg AIDC_TOOLCHAINS=go,node,python \ + --build-arg AIDC_SECURITY_TOOLS= \ + -t aidc-size-probe work + docker images aidc-size-probe --format '{{.Size}}' > /tmp/image-size.txt + SIZE=$(docker images aidc-size-probe --format '{{.Size}}') + echo "IMAGE_SIZE=$SIZE" >> "$GITHUB_ENV" + echo "Built image size: $SIZE" + + - name: Report size on PR + if: github.event_name == 'pull_request' + uses: actions/github-script@v7 + with: + script: | + const size = process.env.IMAGE_SIZE; + const msg = `**aidc image size** (toolchains: go,node,python): \`${size}\`\n\n` + + `Previous reference: ~4–6 GB. Budget is informational for now — ` + + `tighten after the size-reduction PRs land.`; + await github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: msg, + }); + + - name: Budget assertion (soft) + run: | + set -euo pipefail + SIZE=$(docker images aidc-size-probe --format '{{.Size}}') + # 6 GB ceiling as a sanity gate; tighten after optimizations land. + BYTES=$(echo "$SIZE" | awk '{ + if ($0 ~ /GB$/) print $0 * 1024 * 1024 * 1024; + else if ($0 ~ /MB$/) print $0 * 1024 * 1024; + else print $0 + }') + echo "size bytes: $BYTES" + MAX=$((6 * 1024 * 1024 * 1024)) + if [ "$BYTES" -gt "$MAX" ]; then + echo "::warning::image size $SIZE exceeds the 6 GB soft budget" + else + echo "image size $SIZE within budget" + fi diff --git a/lib/aidc.sh b/lib/aidc.sh index c526fd5..da8485d 100644 --- a/lib/aidc.sh +++ b/lib/aidc.sh @@ -497,8 +497,11 @@ aidc::cmd_status_global() { fi # One-shot disk and stats lookups so we don't fork docker per container. - local size_map stats_map running_ids + local size_map stats_map running_ids image_map size_map="$(docker container ls --all --size --format '{{.ID}}|{{.Size}}' 2>/dev/null)" + # Image sizes by image ID, so we can show the on-disk image footprint per + # project without a docker fork per container. + image_map="$(docker image ls --no-trunc --format '{{.ID}}|{{.Size}}' 2>/dev/null)" running_ids="$(printf '%s\n' "$rows" | awk -F'|' '$3=="running" {printf "%s ", $1}')" stats_map='' if [[ -n "$running_ids" ]]; then @@ -515,8 +518,11 @@ aidc::cmd_status_global() { local workspace="${working_dir%/.devcontainer*}" [[ -z "$workspace" ]] && workspace='(unknown)' - local size + local size image_size size="$(printf '%s\n' "$size_map" | awk -F'|' -v id="$id" 'index($1,id)==1 {print $2; exit}')" + local cimage + cimage="$(docker inspect --format '{{.Image}}' "$id" 2>/dev/null || true)" + image_size="$(printf '%s\n' "$image_map" | awk -F'|' -v img="$cimage" 'index($1,img)==1 {print $2; exit}')" if [[ "$state" == "running" ]]; then running=$((running + 1)) @@ -535,6 +541,7 @@ aidc::cmd_status_global() { "$C_LBL" "$C_RST" "${cpu:-?}" \ "$C_LBL" "$C_RST" "${mem:-?}" \ "$C_LBL" "$C_RST" "${pids:-?}" + [[ -n "$image_size" ]] && printf ' %simage%s %s\n' "$C_LBL" "$C_RST" "$image_size" [[ -n "$started" ]] && printf ' %ssince%s %s\n' "$C_LBL" "$C_RST" "$started" else stopped=$((stopped + 1)) @@ -545,6 +552,7 @@ aidc::cmd_status_global() { printf ' %sā—‹ %s%s %s\n' "$C_WARN" "$state" "$C_RST" "$slug" printf ' %s%s%s\n' "$C_DIM" "$workspace" "$C_RST" printf ' %sdisk%s %s\n' "$C_LBL" "$C_RST" "${size:-?}" + [[ -n "$image_size" ]] && printf ' %simage%s %s\n' "$C_LBL" "$C_RST" "$image_size" [[ -n "$exited" ]] && printf ' %sexited%s %s\n' "$C_LBL" "$C_RST" "$exited" fi printf '\n'