Skip to content

feat(dist): packaging, container image, install paths, completions & signed releases#192

Merged
dgenio merged 5 commits into
mainfrom
claude/issue-triage-grouping-xnl2t9
Jun 22, 2026
Merged

feat(dist): packaging, container image, install paths, completions & signed releases#192
dgenio merged 5 commits into
mainfrom
claude/issue-triage-grouping-xnl2t9

Conversation

@dgenio

@dgenio dgenio commented Jun 22, 2026

Copy link
Copy Markdown
Owner

Summary

Implements the distribution / packaging / release-pipeline issue group in one coherent PR (all rooted in .goreleaser.yml + .github/workflows/ + distribution docs). Net of ~938 lines; the bulk of new code is the completion/man generator and its tests, the rest is release config and docs.

What changed (by area):

Related issue

Closes #107
Closes #149
Closes #104
Closes #105
Closes #120
Closes #111

How verified

  • make ci (fmt-check, vet, go test -race + coverage) — all packages pass, total coverage 80.9%.
  • New CLI tests: go test ./cmd/agentfence -run 'Completion|ManPage|TopLevelCommands|RunMan'8/8 pass (each shell script contains every command + subcommand groups; shell markers; unsupported-shell error; man page structure; arg-count guards).
  • goreleaser check with v2.16.0 (the version CI's ~> v2 resolves) — valid, exit 0, zero deprecations (migrated dockersdockers_v2, brewshomebrew_casks).
  • Generated scripts validated: bash -n on the bash completion passes; make completions / make man produce the expected files (gitignored).
  • Version stamping confirmed: -X main.commit/-X main.date print under version; default build stays single-line.
  • All three workflow/goreleaser YAMLs parse.

Tradeoffs / risks

  • Cannot be exercised locally / runs only on a tag: the GHCR push, cosign signing, SBOM, and package-manager publishing run only in the tagged-release pipeline (no Docker daemon in this sandbox; signing needs OIDC). goreleaser check validates the config; the CI goreleaser-check + new docker-build jobs cover the rest on PR.
  • Maintainer setup required before the first signed release (documented in docs/distribution.md): create dgenio/homebrew-tap, dgenio/scoop-bucket, and a dgenio/winget-pkgs fork, and add the HOMEBREW_TAP_GITHUB_TOKEN / SCOOP_BUCKET_GITHUB_TOKEN / WINGET_GITHUB_TOKEN secrets. GHCR and cosign need no extra secrets (workflow GITHUB_TOKEN + OIDC).
  • #123 (Kubernetes/Helm) was intentionally left out as a follow-up — it depends on the published image landing first.

Checklist

  • Tests added or updated
  • Documentation updated if needed
  • CI passes (verified make ci + goreleaser check locally; container/release steps run in CI/on tag)
  • Issue number included

🤖 Generated with Claude Code

https://claude.ai/code/session_01FsYfrSqEBrap5VUSB3NPEu


Generated by Claude Code

claude added 3 commits June 22, 2026 07:41
Add 'agentfence completion <bash|zsh|fish>' and 'agentfence man'
subcommands that emit their artifacts from the CLI's own command list, so
shipped completions/man can never drift from the binary (#107). Also stamp
build provenance: main.commit/main.date are now real symbols surfaced by
'version' when set by the release pipeline (kept single-line for local
builds).

Refs #107, #111

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FsYfrSqEBrap5VUSB3NPEu
Distribution/packaging for the release pipeline:
- Dockerfile (multi-stage, distroless static, non-root) for 'make docker'
  and Dockerfile.goreleaser for the published image; CI builds and
  smoke-tests the image (#149, #104).
- GoReleaser dockers_v2 publishes a multi-arch (amd64/arm64) GHCR image;
  release.yml gains QEMU/buildx, GHCR login, and packages: write (#104).
- scripts/install.sh: checksum-verifying curl|sh installer, fails closed on
  mismatch; Homebrew cask, Scoop and winget manifests via GoReleaser
  (#105, #120).
- cosign keyless signing of checksums and the image manifest, plus a Syft
  SBOM per archive; release.yml gains id-token: write and installs
  cosign/syft (#111).
- Makefile completions/man/docker targets; generated dirs gitignored.

Validated locally with 'goreleaser check' (v2.16.0, the version CI's
'~> v2' resolves): clean, zero deprecations.

Refs #149, #104, #105, #120, #111

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FsYfrSqEBrap5VUSB3NPEu
…ation

Expand README Install with the script/Homebrew/Scoop/winget/container
paths; add install-channel, container, completions, and 'Verifying a
release' (cosign/SBOM) sections to docs/distribution.md including the
maintainer-only tap/bucket/fork prerequisites; record the work under
CHANGELOG [Unreleased].

Refs #104, #105, #107, #111, #120, #149

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FsYfrSqEBrap5VUSB3NPEu
Copilot AI review requested due to automatic review settings June 22, 2026 07:42

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR implements a comprehensive distribution/release pipeline for AgentFence: packaging generated CLI artifacts (completions + man page), adding container image builds/publishing, adding an install script and package-manager channels, and hardening releases with signing/SBOM.

Changes:

  • Add agentfence completion <bash|zsh|fish> and agentfence man, plus generation targets (make completions, make man) and archive bundling.
  • Add container build artifacts (Dockerfile, Dockerfile.goreleaser) plus CI smoke-build and release publishing to GHCR.
  • Extend GoReleaser + release workflow for SBOM generation, cosign signing, and publishing to Homebrew/Scoop/winget.

Reviewed changes

Copilot reviewed 13 out of 14 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
scripts/install.sh New checksum-verifying installer for Linux/macOS releases.
README.md Updates install documentation to include script, package managers, and container image.
Makefile Adds targets for completions/man generation and docker builds; cleans generated artifacts.
docs/distribution.md Documents distribution channels, container usage, completions/man, and verification steps.
Dockerfile.goreleaser Distroless runtime image for GoReleaser-published container builds.
Dockerfile Multi-stage from-source container build for local docker build / CI smoke-tests.
cmd/agentfence/main.go Adds completion/man commands and optional build provenance in version.
cmd/agentfence/completion.go Implements completion + man-page generation from a shared command surface list.
cmd/agentfence/completion_test.go Adds tests to prevent drift and validate completion/man output structure.
CHANGELOG.md Adds an unreleased entry describing new distribution/release features.
.goreleaser.yml Generates artifacts in hooks, bundles them into archives, adds SBOM/signing/images and package manager publishing.
.gitignore Ignores generated completions/manpages directories.
.github/workflows/release.yml Adds GHCR permissions and installs cosign/syft; runs GoReleaser on tags.
.github/workflows/ci.yml Adds docker image build + smoke-test job on CI.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread cmd/agentfence/completion.go
Comment thread cmd/agentfence/completion.go
Comment thread scripts/install.sh
Comment thread .goreleaser.yml Outdated
Comment thread cmd/agentfence/completion_test.go
Comment thread .github/workflows/release.yml
- man: stamp main.Version into the man page's .TH header in the goreleaser
  before-hook (-ldflags) so releases no longer embed 'agentfence dev'
  (verified via snapshot: header shows the release version).
- man: harden manEscape to neutralize a leading '.'/'\'' (roff control
  characters) with a zero-width escape; fix its comment to match.
- release: make Homebrew/Scoop/winget skip_upload conditional on their token
  env so a first tagged release succeeds without the optional secrets
  configured (verified via snapshot — all three pipes run cleanly).
- install.sh: select the checksum with an exact awk field match instead of a
  regex grep, and sanity-check the resolved hash.
- zsh completion: keep $words[1] dispatch (empirically correct under the
  '*::' rest spec: 'audit <TAB>' => $words[1]=audit, $words[2] empty) and
  add a clarifying comment plus structural tests guarding the dispatch and
  the man escaping.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FsYfrSqEBrap5VUSB3NPEu

dgenio commented Jun 22, 2026

Copy link
Copy Markdown
Owner Author

Thanks for the review — addressed in d49b4dc:

  • man page embedded agentfence dev (.goreleaser.yml): good catch. The man before hook now runs go run -ldflags "-X main.Version={{ .Version }}". Verified with a snapshot build — the .TH header reads agentfence 0.0.0-SNAPSHOT-… instead of dev.

  • Release fails on empty publisher tokens (release.yml): correct — empty repository.token would fail those pipes. Made skip_upload conditional on each token's presence for the Homebrew/Scoop/winget blocks, so a tagged release without the optional secrets still succeeds (binaries + image + signing). Verified via snapshot: all three pipes run cleanly with the tokens unset. Updated the workflow comment to match.

  • manEscape only escaped backslashes: hardened to also neutralize a leading ./' with the zero-width \&, and corrected the comment; added TestManEscape.

  • install.sh regex checksum lookup: switched to an exact awk field match ($2 == f) plus a hex sanity check, so the dots in the filename can't match unintended rows.

  • Test coverage for dispatch: added TestZshCompletionDispatch / TestBashCompletionDispatch.

  • zsh $words[1] vs $words[2]: I kept $words[1] here. With the *:: (double-colon) rest-args spec, zsh rescopes $words to the sub-context, so $words[1] is the subcommand. Verified empirically by driving real completion via zpty — for agentfence audit <TAB>: $words[1]=audit, $words[2]= (empty), $line[1]=audit. Switching to $words[2] would break group completion, so I added a regression test and a clarifying comment instead.


Generated by Claude Code

The existing TestTopLevelCommandsMatchDispatch guards only top-level
commands. subcommandGroups (audit/policy/completion second words) had no
equivalent guard, so adding a new `audit`/`policy` subcommand would
silently drop it from generated completions with no failing test.

Add TestSubcommandGroupsMatchDispatch mirroring the house drift-guard
pattern: it asserts each group lists exactly the subcommands its
dispatcher routes (runAuditSubcmd / runPolicySubcmd) and that the
completion group equals supportedShells.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012QsfUcfxTTSRiHbLwfXgUB
@dgenio dgenio merged commit 28b0fb9 into main Jun 22, 2026
6 checks passed
@dgenio dgenio deleted the claude/issue-triage-grouping-xnl2t9 branch June 22, 2026 20:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment