Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
* @basecamp/sip
.goreleaser.yaml @basecamp/sip
.github/workflows/ @basecamp/sip
scripts/release.sh @basecamp/sip
7 changes: 7 additions & 0 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
## What

## Why

## Testing

- [ ] `make check` passes
6 changes: 4 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -107,14 +107,14 @@ jobs:
with:
fetch-depth: 0

- name: Generate token for private SDK access
- name: Generate token for private SDK and tap access
id: sdk-token
uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2
with:
app-id: ${{ vars.RELEASE_CLIENT_ID }}
private-key: ${{ secrets.RELEASE_APP_PRIVATE_KEY }}
owner: basecamp
repositories: basecamp-sdk
repositories: basecamp-sdk,homebrew-tap

- name: Set up Go
uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6
Expand Down Expand Up @@ -234,3 +234,5 @@ jobs:
args: release --clean
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
HOMEBREW_TAP_TOKEN: ${{ steps.sdk-token.outputs.token }}
AUR_KEY: ${{ secrets.AUR_KEY }}
76 changes: 51 additions & 25 deletions .goreleaser.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ before:
hooks:
# PGO profile should be generated by release workflow before GoReleaser runs
- sh -c 'if [ -f default.pgo ]; then echo "PGO profile found ($(du -h default.pgo | cut -f1))"; else echo "No PGO profile - build will use standard optimization"; fi'
- sh -c 'mkdir -p completions && go run ./cmd/basecamp completion bash > completions/basecamp.bash && go run ./cmd/basecamp completion zsh > completions/_basecamp && go run ./cmd/basecamp completion fish > completions/basecamp.fish'

builds:
- id: basecamp
Expand Down Expand Up @@ -43,7 +44,9 @@ archives:
- zip
files:
- LICENSE*
- MIT-LICENSE
- README*
- completions/*

checksum:
name_template: 'checksums.txt'
Expand Down Expand Up @@ -103,29 +106,52 @@ release:

Other platforms: download the matching archive from the assets below.

# Homebrew cask — deferred until cask tap is set up
# homebrew_casks:
# - name: basecamp
# repository:
# owner: basecamp
# name: homebrew-tap
# token: "{{ .Env.HOMEBREW_TAP_TOKEN }}"
# directory: Casks
# homepage: "https://github.com/basecamp/basecamp-cli"
# description: "Command-line interface for Basecamp"
# binaries:
# - basecamp
# skip_upload: auto
homebrew_casks:
- name: basecamp
repository:
owner: basecamp
name: homebrew-tap
token: "{{ .Env.HOMEBREW_TAP_TOKEN }}"
directory: Casks
homepage: "https://github.com/basecamp/basecamp-cli"
description: "Command-line interface for Basecamp"
binaries:
- basecamp
skip_upload: auto

# Scoop (Windows) — deferred, no Windows users yet
# scoops:
# - name: basecamp
# repository:
# owner: basecamp
# name: homebrew-tap
# token: "{{ .Env.HOMEBREW_TAP_TOKEN }}"
# directory: bucket
# homepage: "https://github.com/basecamp/basecamp-cli"
# description: "Command-line interface for Basecamp"
# license: MIT
# skip_upload: auto
scoops:
- name: basecamp
repository:
owner: basecamp
name: homebrew-tap
token: "{{ .Env.HOMEBREW_TAP_TOKEN }}"
homepage: "https://github.com/basecamp/basecamp-cli"
description: "Command-line interface for Basecamp"
license: MIT
skip_upload: auto

aurs:
- name: basecamp-bin
homepage: "https://github.com/basecamp/basecamp-cli"
description: "CLI for Basecamp project management"
maintainers:
- "Basecamp <support@basecamp.com>"
license: "MIT"
private_key: "{{ .Env.AUR_KEY }}"
git_url: "ssh://aur@aur.archlinux.org/basecamp-bin.git"
provides:
- basecamp
conflicts:
- basecamp
- basecamp-cli
optdepends:
- "bash-completion: for bash shell completions"
- "zsh: for zsh shell completions"
- "fish: for fish shell completions"
skip_upload: auto
package: |-
install -Dm755 "./basecamp" "${pkgdir}/usr/bin/basecamp"
install -Dm644 "./MIT-LICENSE" "${pkgdir}/usr/share/licenses/basecamp/MIT-LICENSE"
install -Dm644 completions/basecamp.bash "${pkgdir}/usr/share/bash-completion/completions/basecamp"
install -Dm644 completions/_basecamp "${pkgdir}/usr/share/zsh/site-functions/_basecamp"
install -Dm644 completions/basecamp.fish "${pkgdir}/usr/share/fish/vendor_completions.d/basecamp.fish"
2 changes: 2 additions & 0 deletions .naming-allowlist
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,5 @@ keyring.Get("bcq"
keyring(bcq
# commands.go — catalog description for migrate command
legacy bcq
# RELEASING.md — actual GitHub App name
bcq-release-bot
File renamed without changes.
58 changes: 55 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,11 @@ test:
test-e2e: build
./e2e/run.sh

# Run tests with race detector
.PHONY: race-test
race-test:
BASECAMP_NO_KEYRING=1 $(GOTEST) -race -count=1 ./...

# Run tests with coverage
.PHONY: test-coverage
test-coverage:
Expand Down Expand Up @@ -266,10 +271,31 @@ clean-all: clean clean-pgo
install:
$(GOCMD) install ./cmd/basecamp

# Guard against local replace directives in go.mod
.PHONY: replace-check
replace-check:
@if grep -q '^[[:space:]]*replace[[:space:]]' go.mod; then \
echo "ERROR: go.mod contains replace directives"; \
grep '^[[:space:]]*replace[[:space:]]' go.mod; \
echo ""; \
echo "Remove replace directives before releasing."; \
exit 1; \
fi
@echo "Replace check passed (no local replace directives)"

# Run all checks (local CI gate)
.PHONY: check
check: fmt-check vet lint test test-e2e check-naming check-surface provenance-check tidy-check

# Full pre-flight for release: check + replace-check + vuln + race + surface compat
.PHONY: release-check
release-check: check replace-check vuln race-test check-surface-compat

# Cut a release (delegates to scripts/release.sh)
.PHONY: release
release:
DRY_RUN=$(DRY_RUN) scripts/release.sh $(VERSION)

# Generate CLI surface snapshot (validates binary produces valid output)
.PHONY: check-surface
check-surface: build
Expand All @@ -281,6 +307,26 @@ check-surface: build
check-surface-diff:
scripts/check-cli-surface-diff.sh $(BASELINE) $(CURRENT)

# Check CLI surface compatibility against previous tag (mirrors CI gate)
.PHONY: check-surface-compat
check-surface-compat: build
@scripts/check-cli-surface.sh $(BUILD_DIR)/$(BINARY) /tmp/current-surface.txt
@PREV_TAG=$$(git describe --tags --abbrev=0 HEAD^ 2>/dev/null || echo ""); \
if [ -n "$$PREV_TAG" ]; then \
SCRIPT_DIR="$$(pwd)/scripts"; \
BASELINE_DIR=$$(mktemp -d); \
cleanup() { git worktree remove "$$BASELINE_DIR" --force 2>/dev/null || true; rm -rf "$$BASELINE_DIR" 2>/dev/null || true; }; \
trap cleanup EXIT; \
git worktree add "$$BASELINE_DIR" "$$PREV_TAG" || { echo "Failed to create worktree for $$PREV_TAG"; exit 1; }; \
cd "$$BASELINE_DIR" && make build && \
"$$SCRIPT_DIR/check-cli-surface.sh" ./bin/basecamp /tmp/baseline-surface.txt; \
cd - >/dev/null; \
cleanup; trap - EXIT; \
scripts/check-cli-surface-diff.sh /tmp/baseline-surface.txt /tmp/current-surface.txt; \
else \
echo "First release — no baseline to compare against"; \
fi

# Guard against bcq/BCQ creeping back (allowlist in .naming-allowlist)
.PHONY: check-naming
check-naming:
Expand Down Expand Up @@ -366,6 +412,7 @@ help:
@echo "Test:"
@echo " test Run Go unit tests"
@echo " test-e2e Run end-to-end tests against Go binary"
@echo " race-test Run tests with race detector"
@echo " test-coverage Run tests with coverage report"
@echo ""
@echo "Performance:"
Expand Down Expand Up @@ -400,9 +447,14 @@ help:
@echo " clean Remove build artifacts"
@echo " clean-all Remove all artifacts (including PGO)"
@echo " install Install to GOPATH/bin"
@echo " check Run all checks (local CI gate)"
@echo " check-naming Guard against stale bcq/BCQ references"
@echo " run Build and run"
@echo " check Run all checks (local CI gate)"
@echo " check-naming Guard against stale bcq/BCQ references"
@echo " replace-check Guard against local replace directives in go.mod"
@echo " run Build and run"
@echo ""
@echo "Release:"
@echo " release-check Full pre-flight (check + replace-check + vuln + race + surface compat)"
@echo " release Cut a release (VERSION=x.y.z, DRY_RUN=1 optional)"
@echo ""
@echo "Security:"
@echo " security Run all security checks (lint, vuln, secrets)"
Expand Down
14 changes: 11 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,24 @@ That's it. You now have full access to Basecamp from your terminal.
<details>
<summary>Other installation methods</summary>

**Go install:**
**Scoop (Windows):**
```bash
go install github.com/basecamp/basecamp-cli/cmd/basecamp@latest
scoop bucket add basecamp https://github.com/basecamp/homebrew-tap
scoop install basecamp
```

**Shell script:**
```bash
curl -fsSL https://raw.githubusercontent.com/basecamp/basecamp-cli/main/scripts/install.sh | bash
```

**Go install:**
```bash
go install github.com/basecamp/basecamp-cli/cmd/basecamp@latest
```

**GitHub Release:** download from [Releases](https://github.com/basecamp/basecamp-cli/releases).

</details>

## Usage
Expand Down Expand Up @@ -127,4 +135,4 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup.

## License

[MIT](LICENSE.md)
[MIT](MIT-LICENSE)
67 changes: 67 additions & 0 deletions RELEASING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# Releasing

## Quick release

```bash
make release VERSION=0.2.0
```

## Dry run

```bash
make release VERSION=0.2.0 DRY_RUN=1
```

## What happens

1. Validates semver format, main branch, clean tree, synced with remote
2. Checks for `replace` directives in go.mod
3. Runs `make release-check` (quality checks, vuln scan, replace-check, race-test, surface compat)
4. Creates annotated tag `v$VERSION` and pushes to origin
5. GitHub Actions [release workflow](.github/workflows/release.yml) runs:
- Security scan + full test suite + CLI surface compatibility check
- Collects PGO profile from benchmarks
- Generates AI changelog from commit history
- Builds binaries for all platforms (darwin, linux, windows, freebsd, openbsd × amd64/arm64)
- Signs checksums with cosign (keyless via Sigstore OIDC)
- Generates SBOM for supply chain transparency
- Updates Homebrew cask in `basecamp/homebrew-tap`
- Updates Scoop manifest in `basecamp/homebrew-tap`
- Updates AUR `basecamp-bin` package (when `AUR_KEY` is configured)

## Versioning

Pre-1.0: minor bumps for features, patch bumps for fixes. Prerelease tags
(e.g. `0.2.0-rc.1`) are marked as prereleases automatically by GoReleaser.

## Requirements

- On `main` branch with clean, synced working tree
- No `replace` directives in go.mod
- `make release-check` passes (includes check, replace-check, vuln scan, race-test, surface compat)

## CI secrets

| Secret | Purpose |
|--------|---------|
| `RELEASE_CLIENT_ID` (var) | GitHub App ID for `bcq-release-bot` |
| `RELEASE_APP_PRIVATE_KEY` | GitHub App private key |
| `AUR_KEY` | SSH private key for AUR push (optional) |

## AUR setup (one-time)

1. Create an account at https://aur.archlinux.org
2. Register the `basecamp-bin` package
3. Generate an SSH keypair: `ssh-keygen -t ed25519 -f aur_key -C "basecamp-cli AUR"`
4. Add the public key to your AUR profile
5. Add the private key as `AUR_KEY` in GitHub Actions secrets

## Distribution channels

| Channel | Location | Updated by |
|---------|----------|------------|
| GitHub Releases | [basecamp/basecamp-cli](https://github.com/basecamp/basecamp-cli/releases) | GoReleaser |
| Homebrew cask | `basecamp/homebrew-tap` Casks/ | GoReleaser |
| Scoop | `basecamp/homebrew-tap` root | GoReleaser |
| AUR | `basecamp-bin` | GoReleaser |
| go install | `go install github.com/basecamp/basecamp-cli/cmd/basecamp@latest` | Go module proxy |
15 changes: 10 additions & 5 deletions install.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ Install the Basecamp CLI and connect it to your AI agent. Execute all steps auto

Choose ONE installation method:

### Option A: Homebrew (macOS/Linux) - Recommended
### Option A: Homebrew (macOS/Linux) Recommended
```bash
brew install --cask basecamp/tap/basecamp
```
Expand All @@ -29,16 +29,21 @@ scoop bucket add basecamp https://github.com/basecamp/homebrew-tap
scoop install basecamp
```

### Option C: Go install
### Option C: Shell script
```bash
go install github.com/basecamp/basecamp-cli/cmd/basecamp@latest
curl -fsSL https://raw.githubusercontent.com/basecamp/basecamp-cli/main/scripts/install.sh | bash
```

### Option D: Shell script
The install script downloads the latest release, verifies the SHA-256 checksum, and verifies the cosign signature when cosign is available.

### Option D: Go install
```bash
curl -fsSL https://raw.githubusercontent.com/basecamp/basecamp-cli/main/scripts/install.sh | bash
go install github.com/basecamp/basecamp-cli/cmd/basecamp@latest
```

### Option E: GitHub Release
Download the archive for your platform from [Releases](https://github.com/basecamp/basecamp-cli/releases), extract, and move `basecamp` to a directory on your PATH.

**Verify:**
```bash
basecamp --version
Expand Down
Loading
Loading