---
name: publish-docs
description: Publish the docs site to GHCR with a main-<sha> tag and bump site/helm/values.yaml so Argo CD picks it up — without cutting a SemVer release. Use when docs/site changes need to go live between releases.
user-invocable: true
---

# Publish Docs (no new release)

Publishes a docs-only image to `ghcr.io/vfarcic/dot-agent-deck-docs` with a `main-<short-sha>` tag and updates `site/helm/values.yaml` so Argo CD picks it up. Does **not** create a release, version bump, binary build, Homebrew formula, Scoop manifest, or GitHub release.

## What a publish builds

The site is generated by `cargo xtask site` (`xtask/site`) from `docs/published.toml`: the landing page from `site/landing/`, every manifest page as raw Markdown at `/docs/<slug>.md`, `llms.txt` and `llms-full.txt`, the images under `site/static/img/` (`docs/img` is a symlink to it), and the redirects from the old Docusaurus URLs. There is no Node and no npm anywhere in it. `.github/workflows/docs-publish.yml` runs three jobs:

1. **`site-build`** checks out `main` with a read-only token and no secret, records the commit, and runs `cargo xtask site site/build/public --nginx-redirects site/build/nginx-redirects.conf`. `site/build` is uploaded as the `docs-site` artifact, which both deploy paths consume, so they publish the same bytes.
2. **`publish`** checks out that same commit with `RELEASE_TOKEN` (it fails at its first step if the secret is empty), downloads the artifact, and builds `site/Dockerfile`, which has no build stage: it copies `site/build/public` into nginx's root, `site/build/nginx-redirects.conf` to `/etc/nginx/site-redirects.conf` as an nginx config include, outside the served files, and `site/nginx-default.conf` as the server config. It pushes the image, sets `image.tag` in `site/helm/values.yaml` (and `Chart.yaml`'s version and appVersion on the release path), commits that, and pushes the commit straight to `main`.
3. **`netlify-deploy`** deploys the same artifact's `build/public` to Netlify production with `--no-build`, after `publish` succeeds.

## When to Use

- Docs / site changes have been merged to `main` and you want them live now.
- You don't want to cut a SemVer release just for documentation.

## When NOT to Use

- You're cutting a versioned release — use `/tag-release` instead. The release workflow already publishes docs as part of the release via the same underlying `docs-publish.yml` workflow.
- You have un-released non-docs (code) changes that should also ship — cut a release.

## Workflow

### Step 1: Sync main locally

The workflow dispatches against `origin/main`, so make sure you know what's there:

```bash
git fetch origin
git checkout main
git pull --rebase origin main
```

If the user is in a worktree, fetch is enough — they don't need to switch branches; `gh workflow run --ref main` dispatches against the remote ref regardless of local checkout.

### Step 2: Confirm there are docs/site changes since the last release

```bash
LAST_TAG=$(git tag --list 'v*' --sort=-v:refname | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' | head -1)
echo "Last release: ${LAST_TAG}"
git log --oneline "${LAST_TAG}..origin/main" -- docs/ site/ xtask/site/ src/published_docs.rs
git diff --stat "${LAST_TAG}..origin/main" -- docs/ site/ xtask/site/ src/published_docs.rs
```

`xtask/site/` and `src/published_docs.rs` are the generator and the manifest parser, so a change there can change the published output even when no page did. A change under `docs/develop/` changes nothing on the site; disregard it.

If there are no docs/site changes since the last release, inform the user and stop — there is nothing meaningful to publish.

### Step 2b: Build the site locally (optional)

The same command the workflow runs, so a broken link or a manifest error shows up here rather than in the run. It needs only the Rust toolchain, and refuses an output directory that is not empty. Run it in a checkout at `origin/main` — the user's own, or a detached worktree at a disk-backed sibling path (CLAUDE.md rule 14, since it compiles the `xtask` crates), which you ask about before creating:

```bash
rm -rf site/build   # generated output, gitignored
cargo xtask site site/build/public --nginx-redirects site/build/nginx-redirects.conf
```

Skip it when the user has just done this themselves on that commit.

### Step 3: Show the user what will change

Present:
- **Current chart tag**: read from `site/helm/values.yaml` `image.tag` (e.g. `v0.26.0`).
- **New tag**: `main-<short-sha>` where short-sha is `git rev-parse --short=7 origin/main`.
- **Commits included**: the list from Step 2.

Ask the user to confirm before triggering the workflow.

### Step 4: Trigger the workflow

```bash
gh workflow run docs-publish.yml --ref main
```

### Step 5: Watch the run

```bash
sleep 5
RUN_ID=$(gh run list --workflow=docs-publish.yml --branch=main --limit 1 --json databaseId --jq '.[0].databaseId')
gh run watch "$RUN_ID"
```

### Step 6: Report result

On success, tell the user:
- The image tag that was pushed (`main-<sha>`).
- That a `chore: publish docs image main-<sha> [skip ci]` commit was pushed to `main` — they should `git pull` to pick it up.
- Argo CD will detect the `values.yaml` change and sync within a minute or two; the site at https://agent-deck.devopstoolkit.ai will update shortly after.
- The chart now points at a `main-<sha>` tag. The next `/tag-release` will re-pin it to `v<semver>` automatically.
- The same run also deploys that commit's build to Netlify production (site `agent-deck-devopstoolkit-ai`, URL in the run summary). This runs alongside the cluster during the migration to Netlify; until DNS is switched, the Netlify copy is not what `agent-deck.devopstoolkit.ai` serves. A failed Netlify step does not undo the image or the chart bump, which run first.

## Notes

- **Same sha → no-op**: re-running on a SHA that's already published is harmless — the workflow pushes the same image bytes and the `values.yaml` diff is empty, so no commit happens.
- **`:latest` is untouched**: manual runs never push or move the `:latest` tag. That tag follows formal releases only.
- **Not for release flows**: do not run this inside `/pr-create`, `/prd-full`, or any release skill — it would interfere with the release path's own docs publish step.
- **No changelog fragment**: a docs-only publish is not a release, so no entry in `changelog.d/` is needed.
- **The binary's docs do not move**: `dot-agent-deck docs` prints the pages embedded when that binary was built, so a docs publish updates the website and its `llms` files only. Installed binaries pick up doc changes with the next release.
