diff --git a/.github/workflows/docker.lint.yml b/.github/workflows/feature.yml similarity index 54% rename from .github/workflows/docker.lint.yml rename to .github/workflows/feature.yml index fddd757..b26bdf4 100644 --- a/.github/workflows/docker.lint.yml +++ b/.github/workflows/feature.yml @@ -1,5 +1,5 @@ --- -name: lint +name: feature on: push: @@ -12,7 +12,7 @@ permissions: jobs: hadolint: name: hadolint - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - name: checkout uses: actions/checkout@v7 @@ -24,7 +24,7 @@ jobs: trivy-scan: name: trivy-scan - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - name: checkout uses: actions/checkout@v7 @@ -39,7 +39,7 @@ jobs: actionlint: name: actionlint - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - name: checkout uses: actions/checkout@v7 @@ -48,3 +48,21 @@ jobs: uses: raven-actions/actionlint@v2 with: fail-on-error: true + + gitguardian-scan: + name: gitguardian-scan + runs-on: ubuntu-24.04 + permissions: + contents: read + steps: + - name: checkout + uses: actions/checkout@v7 + with: + fetch-depth: 0 # fetch all history so multiple commits can be scanned + - name: scan + uses: GitGuardian/ggshield-action@v1 + env: + GITHUB_PUSH_BEFORE_SHA: ${{ github.event.before }} + GITHUB_PUSH_BASE_SHA: ${{ github.event.base }} + GITHUB_DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} + GITGUARDIAN_API_KEY: ${{ secrets.GITGUARDIAN_API_KEY }} diff --git a/.github/workflows/gitguardian.yml b/.github/workflows/gitguardian.yml deleted file mode 100644 index 99624d6..0000000 --- a/.github/workflows/gitguardian.yml +++ /dev/null @@ -1,27 +0,0 @@ ---- -# yamllint disable rule:line-length -name: gitguardian-scan - -on: - push: - branches-ignore: - - main - -jobs: - scanning: - name: scan - runs-on: ubuntu-latest - permissions: - contents: read - steps: - - name: checkout - uses: actions/checkout@v7 - with: - fetch-depth: 0 # fetch all history so multiple commits can be scanned - - name: scan - uses: GitGuardian/ggshield-action@v1 - env: - GITHUB_PUSH_BEFORE_SHA: ${{ github.event.before }} - GITHUB_PUSH_BASE_SHA: ${{ github.event.base }} - GITHUB_DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} - GITGUARDIAN_API_KEY: ${{ secrets.GITGUARDIAN_API_KEY }} diff --git a/.github/workflows/docker.pr.yml b/.github/workflows/pr.yml similarity index 83% rename from .github/workflows/docker.pr.yml rename to .github/workflows/pr.yml index ba7d90c..4a240a4 100644 --- a/.github/workflows/docker.pr.yml +++ b/.github/workflows/pr.yml @@ -1,5 +1,5 @@ --- -name: pr-build-and-scan-docker-image +name: pr on: pull_request: @@ -20,9 +20,40 @@ permissions: contents: read jobs: + claude-review: + name: claude-review + if: github.event.action == 'opened' + runs-on: ubuntu-24.04 + permissions: + contents: read + pull-requests: write # post the review as a PR comment + id-token: write + steps: + - name: checkout + uses: actions/checkout@v7 + with: + fetch-depth: 1 + + - name: run-claude-review + uses: anthropics/claude-code-action@v1 + with: + anthropic_api_key: ${{ secrets.OPENROUTER_API_KEY }} + prompt: | + REPO: ${{ github.repository }} + PR NUMBER: ${{ github.event.pull_request.number }} + Please review this pull request for correctness bugs, security issues, and + simplification opportunities. + Use `gh pr comment` for top-level feedback. + Use `mcp__github_inline_comment__create_inline_comment` for line comments. + claude_args: | + --allowedTools "mcp__github_inline_comment__create_inline_comment,Bash(gh pr comment:*),Bash(gh pr diff:*),Bash(gh pr view:*)" + env: + ANTHROPIC_BASE_URL: https://openrouter.ai/api + ANTHROPIC_API_KEY: ${{ secrets.OPENROUTER_API_KEY }} + scan: name: build-and-scan - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 permissions: contents: read security-events: write # upload Trivy SARIF to Code Scanning @@ -164,3 +195,4 @@ jobs: echo "| SARIF | Security › Code scanning alerts |" echo "| Push | skipped (PR gate) |" } >> "$GITHUB_STEP_SUMMARY" + diff --git a/.github/workflows/docker.release.yml b/.github/workflows/release.yml similarity index 98% rename from .github/workflows/docker.release.yml rename to .github/workflows/release.yml index 206e2e8..556063a 100644 --- a/.github/workflows/docker.release.yml +++ b/.github/workflows/release.yml @@ -1,5 +1,5 @@ --- -name: release-docker-image +name: release on: push: @@ -28,7 +28,7 @@ jobs: # off-main tags (the tag ref itself still exists in Git; nothing is released). tag-ref-gate: name: tag-ref-gate - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 permissions: contents: read steps: @@ -51,7 +51,7 @@ jobs: push: name: build-and-push needs: tag-ref-gate - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 permissions: contents: read id-token: write # OIDC for cosign keyless signing diff --git a/Dockerfile b/Dockerfile index b322e22..9b7f1fc 100644 --- a/Dockerfile +++ b/Dockerfile @@ -150,13 +150,16 @@ ARG XDG_DATA_HOME=${HOME}/.local/share WORKDIR ${DOTFILES_DIR} +COPY ./stow-packages /tmp/stow-packages + RUN git clone -q --depth=1 -b "main" --single-branch "${DOTFILES_REPO_URL}" "${DOTFILES_DIR}" \ && eval "$(mise hook-env)" \ - && cat ./stow/linux-essential | xargs -I {} stow {} \ + && cat /tmp/stow-packages | xargs -I {} stow {} \ && nvim --headless "+Lazy! restore" +qa \ && pi install git:github.com/leonidgrishenkov/pi-extensions \ && npx -y github:leonidgrishenkov/agent-skills install --target pi --target claude \ - && bat cache --build + && bat cache --build \ + && rm /tmp/stow-packages # Overwrite Mason's prebuilt shfmt (built with Go 1.26.1, vuln stdlib) with the # source-built copy from the Go step above (built with Go 1.26.5). Mason names the diff --git a/README.md b/README.md index 2adb575..82e0472 100644 --- a/README.md +++ b/README.md @@ -11,11 +11,11 @@ workflows are self-contained in this repository under `.github/workflows/`. ### Workflow Triggers -| Trigger | Workflow | What runs | Purpose | -| -------------------------------- | -------------------- | ---------------------------------------------------------------------------------------------------------------------------- | -------------------------------- | -| Push to any branch except `main` | `docker.lint.yml` | `hadolint` Dockerfile lint | Fast feedback during development | -| PR into `main` | `docker.pr.yml` | Build amd64 image, Trivy scan (HIGH/CRITICAL), SARIF upload, smoke test | Gate — must pass before merge | -| Push tag `*.*` | `docker.release.yml` | Verify tag is on `main`, build multi-arch (`linux/amd64`, `linux/arm64`), push to YC CR, SBOM + SLSA provenance, cosign sign | Release | +| Trigger | Workflow | What runs | Purpose | +| -------------------------------- | ------------- | ---------------------------------------------------------------------------------------------------------------------------- | -------------------------------- | +| Push to any branch except `main` | `feature.yml` | `hadolint` Dockerfile lint, Trivy config scan, `actionlint`, GitGuardian secret scan | Fast feedback during development | +| PR into `main` | `pr.yml` | Build amd64 image, Trivy scan (HIGH/CRITICAL), SARIF upload, smoke test, Claude code review (on open) | Gate — must pass before merge | +| Push tag `*.*` | `release.yml` | Verify tag is on `main`, build multi-arch (`linux/amd64`, `linux/arm64`), push to YC CR, SBOM + SLSA provenance, cosign sign | Release | ### Design Goals @@ -29,7 +29,7 @@ workflows are self-contained in this repository under `.github/workflows/`. ### Security Checks -- **GitGuardian Scan** (`gitguardian.yml`) — runs on every push to `main` and every PR into `main` to detect leaked +- **GitGuardian Scan** (part of `feature.yml`) — runs on every push to any branch except `main` to detect leaked secrets. - **Trivy Image Scan** — runs during PR gate with severity `HIGH,CRITICAL` and `ignore-unfixed: true`. Findings are uploaded to GitHub Code Scanning alerts. @@ -37,6 +37,17 @@ workflows are self-contained in this repository under `.github/workflows/`. of `main`, preventing releases from arbitrary branches. - **Image Signing** — every release image is signed with **cosign** using keyless OIDC via GitHub Actions. +### Claude Code Review + +- **`claude-review`** (part of `pr.yml`) — runs once, when a PR into `main` is **opened**, and posts an automated + code review (top-level + inline comments via `gh pr comment` / inline PR comments) using + [`anthropics/claude-code-action`](https://github.com/anthropics/claude-code-action). +- Authenticates through **OpenRouter** rather than a direct Anthropic API key: the action's `ANTHROPIC_BASE_URL` is + redirected to `https://openrouter.ai/api`, using `OPENROUTER_API_KEY` as the credential. This is an unofficial but + working setup (OpenRouter exposes an Anthropic-compatible endpoint) — not a first-class integration, so it can break + on action/CLI updates. +- Does **not** gate the merge — it's advisory feedback only, separate from the `build-and-scan` job. + ### Secrets & Variables | Name | Type | Source | Purpose | @@ -44,13 +55,14 @@ workflows are self-contained in this repository under `.github/workflows/`. | `YC_REGISTRY_ID` | variable | `vars.YC_REGISTRY_ID` | Yandex Container Registry ID | | `YC_CR_SA_AUTH_JSON` | secret | `secrets.YC_CR_SA_AUTH_JSON` | YC service account JSON key for registry login | | `GITHUB_TOKEN` | secret | auto-provided | Authenticated GitHub API requests for `mise` package downloads | +| `OPENROUTER_API_KEY` | secret | `secrets.OPENROUTER_API_KEY` | OpenRouter token used to authenticate the Claude PR review | ### Cache Strategy PR and release builds share a **registry cache** stored in YC CR at `cr.yandex//github/personal/cache/devcr:latest`. -- **PR build** (`docker.pr.yml`) writes cache after scanning. -- **Release build** (`docker.release.yml`) reads the same cache before building multi-arch, significantly reducing +- **PR build** (`pr.yml`) writes cache after scanning. +- **Release build** (`release.yml`) reads the same cache before building multi-arch, significantly reducing redundant layer builds. This avoids the limitations of GHA cache (`type=gha`), where PR caches are isolated to the merge ref and invisible to diff --git a/mise.toml b/mise.toml index ad58a14..8c7bb5c 100644 --- a/mise.toml +++ b/mise.toml @@ -22,6 +22,7 @@ go = "1.26.5" "aqua:fish-shell/fish-shell" = "4.8.0" "aqua:tealdeer-rs/tealdeer" = "1.8.1" claude = "2.1.206" +uv = "0.11.29" [settings] experimental = false diff --git a/stow-packages b/stow-packages new file mode 100644 index 0000000..1dc7f11 --- /dev/null +++ b/stow-packages @@ -0,0 +1,24 @@ +atuin +bat +btop +delta +editorconfig +fish +fsh +ghostty +glow +markdownlint-cli2 +nvim +prettier +ruff +sqlfluff +sqlite +starship +yamlfmt +yazi +zsh +pi +tealdeer +direnv +lazygit +claude