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
36 changes: 36 additions & 0 deletions .github/actions/setup/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
name: setup
description: Install the pinned toolchain and workspace dependencies, with caching

runs:
using: composite
steps:
# installs the toolchain pinned in mise.toml (tool installs are cached by the action)
- uses: jdx/mise-action@v4

- name: resolve cache paths
id: paths
shell: bash
run: |
echo "pnpm-store=$(pnpm store path --silent)" >> "$GITHUB_OUTPUT"
echo "go-mod=$(go env GOMODCACHE)" >> "$GITHUB_OUTPUT"
echo "go-build=$(go env GOCACHE)" >> "$GITHUB_OUTPUT"

- uses: actions/cache@v6
with:
path: ${{ steps.paths.outputs.pnpm-store }}
key: pnpm-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }}
restore-keys: pnpm-${{ runner.os }}-

# module downloads, compiled packages, cached test results and lint results;
# mise.toml is in the key because it pins the go and golangci-lint versions
- uses: actions/cache@v6
with:
path: |
${{ steps.paths.outputs.go-mod }}
${{ steps.paths.outputs.go-build }}
~/.cache/golangci-lint
key: go-${{ runner.os }}-${{ hashFiles('mise.toml', '**/go.sum', 'go.work.sum') }}
restore-keys: go-${{ runner.os }}-

- shell: bash
run: pnpm install --frozen-lockfile
99 changes: 99 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
# Dependabot configuration
#
# Strategy:
# - Security patches flow through promptly (via Dependabot security updates,
# enabled in repo Settings -> Code security). These are not gated by the
# weekly schedule below.
# - Version updates run weekly; patch + minor are grouped into one PR per
# ecosystem to keep the noise down.
# - Major updates are opened individually (not grouped) so each gets a
# deliberate review.
# - `cooldown` lets new releases soak before we adopt them, mitigating
# supply-chain attacks on fresh versions. We soak longer for majors, which
# also naturally spaces them out to roughly monthly.
#
# Note: Dependabot allows only one schedule per ecosystem+directory, so we can't
# set a separate "monthly majors" cadence directly. The 30-day major cooldown
# below achieves the same effect — majors only surface once they've aged a month.
#
# Docs: https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file

version: 2

updates:
# JavaScript / TypeScript — pnpm workspace (single lockfile at root)
- package-ecosystem: "npm"
directory: "/"
schedule:
interval: "weekly"
day: "monday"
time: "06:00"
timezone: "Etc/UTC"
open-pull-requests-limit: 10
cooldown:
semver-patch-days: 3
semver-minor-days: 5
semver-major-days: 30
default-days: 5
groups:
npm-minor-and-patch:
applies-to: version-updates
update-types:
- "minor"
- "patch"
commit-message:
prefix: "deps"
prefix-development: "deps-dev"
include: "scope"

# Go modules — glob matches any directory with a go.mod at any depth, so new
# modules are picked up without listing each one. Dependabot updates each
# module independently (it does not read go.work).
- package-ecosystem: "gomod"
directories:
- "/**"
schedule:
interval: "weekly"
day: "monday"
time: "06:00"
timezone: "Etc/UTC"
open-pull-requests-limit: 10
cooldown:
semver-patch-days: 3
semver-minor-days: 5
semver-major-days: 30
default-days: 5
groups:
gomod-minor-and-patch:
applies-to: version-updates
update-types:
- "minor"
- "patch"
commit-message:
prefix: "deps"
include: "scope"

# GitHub Actions — workflows + composite actions under .github/
- package-ecosystem: "github-actions"
directories:
- "/"
- "/.github/actions/*"
schedule:
interval: "weekly"
day: "monday"
time: "06:00"
timezone: "Etc/UTC"
open-pull-requests-limit: 10
# GitHub Actions aren't versioned with semver, so Dependabot only honors
# `default-days` here (the `semver-*-days` keys are rejected for this ecosystem).
cooldown:
default-days: 5
groups:
actions-minor-and-patch:
applies-to: version-updates
update-types:
- "minor"
- "patch"
commit-message:
prefix: "ci"
include: "scope"
29 changes: 29 additions & 0 deletions .github/workflows/main-build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
name: main-build

on:
push:
branches: [main]

# don't cancel: every commit on main gets a full verification
concurrency:
group: main-build
cancel-in-progress: false

permissions:
contents: read

jobs:
build-and-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7

# toolchain, caches and pnpm install
- uses: ./.github/actions/setup

# prettier over the whole repo (Go is formatted by golangci-lint)
- run: pnpm format:check

# lint, build and test each depend on generate (build also on its
# dependencies' build), so nx runs generate once and shares it
- run: pnpm exec nx run-many -t lint build test
31 changes: 31 additions & 0 deletions .github/workflows/pr-build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
name: pr-build

on:
pull_request:
branches: [main]

concurrency:
group: pr-build-${{ github.ref }}
cancel-in-progress: true

permissions:
contents: read

jobs:
build-and-test:
runs-on: ubuntu-latest
steps:
# full history so nx can diff against origin/main
- uses: actions/checkout@v7
with:
fetch-depth: 0

# toolchain, caches and pnpm install
- uses: ./.github/actions/setup

# prettier over the whole repo (Go is formatted by golangci-lint)
- run: pnpm format:check

# lint, build and test (each depends on generate, build also on its
# dependencies' build), only for projects the PR affects
- run: pnpm affected
19 changes: 19 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
.claude/settings.local.json
.idea/
.vscode/

node_modules/
build/
dist/
out/


.pnpm-store/

# tool caches: TanStack Router's generator temp files, Vite's dep cache
.tanstack/
.vite/

.nx/cache
.nx/workspace-data
.nx/migrate-runs
9 changes: 9 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
.claude/
.idea/
.vscode/
.pnpm-store/
.nx/

pnpm-lock.yaml

node_modules/
44 changes: 44 additions & 0 deletions .sandbox/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# .sandbox

[Docker Sandboxes](https://docs.docker.com/ai/sandboxes/) setup for this workspace: Docker's Claude kit as the workload, plus two [mixin kits](https://docs.docker.com/ai/sandboxes/customize/use-kits/) of ours.

## Usage

From the repo root, create the sandbox or reopen it if it already exists:

```bash
.sandbox/run.sh [name] # name defaults to "monorepo"
```

The Claude kit asks to approve its Anthropic credentials. Select **3. No** (start without them).

Inside the sandbox, run `mise install` and `pnpm install`. Use `sbx ls` to list sandboxes and `sbx rm <name>` to remove one.

## Kits

Both are attached by `run.sh`.

### mise

Sets up the toolchain pinned in `mise.toml`:

- installs the [mise](https://mise.jdx.dev) binary to `/usr/local/bin` (release pinned by `version` in `mise/mise.yaml`)
- puts the mise shims on `PATH`, so installed tools resolve in every shell without `mise activate`
- exports `GOPATH=/home/agent/go`, since the base image's `/go` isn't writable by the agent user

### network-policy

A declaration-only kit listing the outbound hosts the toolchain may reach: what mise needs to install the tools, plus the Go and npm registries and the shadcn component registry. Anything not listed stays blocked by the sandbox's default-deny policy, so the allowlist is reviewable in git. Hosts the agent itself needs, such as `api.anthropic.com`, come with the Claude kit.

To change it:

1. Add or remove a host in `network-policy/network-policy.yaml`.
2. Create a new sandbox with `.sandbox/run.sh <new-name>`, since a sandbox keeps the kits it was created with.
3. Commit the change.

Kit rules can't grant access beyond your organization's policy. To debug a blocked request:

```bash
sbx policy log # recent connections and why they were blocked
sbx policy ls # active rules
```
8 changes: 8 additions & 0 deletions .sandbox/mise/mise.dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# syntax=docker/dockerfile:1
FROM alpine:3.20 AS fetch
ARG MISE_VERSION
RUN apk add --no-cache curl ca-certificates \
&& MISE_INSTALL_PATH=/tmp/mise sh -c "$(curl -fsSL https://mise.run)"

FROM scratch AS content
COPY --from=fetch /tmp/mise /usr/local/bin/mise
41 changes: 41 additions & 0 deletions .sandbox/mise/mise.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# syntax=docker/sandbox-kit:3
schemaVersion: "3"
kind: mixin
displayName: mise
description: Installs mise so the toolchain pinned in mise.toml can be installed in the sandbox.
dockerfile: mise.dockerfile

args:
version:
default: "v2026.11.3"
pattern: '^v[0-9]+\.[0-9]+\.[0-9]+$'
description: mise release to install
buildArg: MISE_VERSION

capabilities:
- type: com.docker.sandbox/network-policy@1
config:
runtime:
allow:
- mise.jdx.dev
- mise-versions.jdx.dev
- github.com
- objects.githubusercontent.com
# The base image sets GOPATH=/go, which the agent user can't write to, and a
# kit can't override image env (sbx rejects the conflict). Export it from the
# sandbox's persistent env file instead, which every shell sources.
- type: com.docker.sandbox/lifecycle@1
config:
startup:
- description: Point GOPATH at a writable directory
user: root
command: >-
grep -qs '^export GOPATH=' /etc/sandbox-persistent.sh ||
echo 'export GOPATH=/home/agent/go' >> /etc/sandbox-persistent.sh
# Non-interactive shells never run `mise activate` hooks, so put the
# shims on PATH: tools installed by `mise install` then resolve everywhere.
- description: Put mise shims on PATH
user: root
command: >-
grep -qs 'mise/shims' /etc/sandbox-persistent.sh ||
echo 'export PATH="/home/agent/.local/share/mise/shims:$PATH"' >> /etc/sandbox-persistent.sh
51 changes: 51 additions & 0 deletions .sandbox/network-policy/network-policy.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# syntax=docker/sandbox-kit:3
# Network allowlist for this workspace, as a declaration-only mixin.
# Anything not listed here stays blocked by the sandbox's default-deny policy.
schemaVersion: "3"
kind: mixin
displayName: Monorepo network policy
description: Hosts mise needs to install the toolchain pinned in mise.toml.

capabilities:
- type: com.docker.sandbox/network-policy@1
config:
runtime:
allow:
# mise itself: version index
- mise.jdx.dev
- mise-versions.jdx.dev

# aqua backend (air, atlas, buf, golangci-lint, pnpm, protoc-gen-go,
# sqlc): registry (raw.githubusercontent.com), version lookup and
# release assets from GitHub
- github.com
- api.github.com
- raw.githubusercontent.com
- objects.githubusercontent.com
- release-assets.githubusercontent.com

# aqua downloads that aren't served from GitHub: atlas (release.ariga.io)
# and sqlc (downloads.sqlc.dev)
- release.ariga.io
- downloads.sqlc.dev

# core:go (go): release index and downloads
- go.dev
- dl.google.com

# core:node (node): release index and downloads
- nodejs.org

# go backend (protoc-gen-connect-go): go install via the module proxy
- proxy.golang.org
- sum.golang.org

# connectrpc.com: vanity import path resolved for protoc-gen-connect-go
# and the connectrpc.com/connect module
- connectrpc.com

# npm backend (@bufbuild/protoc-gen-es, @fission-ai/openspec)
- registry.npmjs.org

# shadcn CLI (ui-kit): component registry and schema
- ui.shadcn.com
20 changes: 20 additions & 0 deletions .sandbox/run.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#!/usr/bin/env bash
# Start (or reopen) the workspace sandbox with the workspace kits attached.
# Usage: .sandbox/run.sh [name] (default name: monorepo)
set -euo pipefail

name="${1:-"base-template"}"
cd "$(dirname "$0")/.."

if sbx ls | awk -v n="$name" '$1 == n { found = 1 } END { exit !found }'; then
exec sbx run --name "$name"
fi

# v3 kits need exactly one workload kit plus any mixins; the built-in "claude"
# agent is not a v3 kit, so it cannot fill that role. Docker's published Claude
# workload kit does, pinned to a tag.
claude_kit="docker.io/docker/sbx-kit-claude:2.1.316"

exec sbx run "$claude_kit" --name "$name" \
--kit ./.sandbox/mise \
--kit ./.sandbox/network-policy
Loading
Loading