---
name: numericalos-publish-site
description: >
  Build and publish the full NumericalOS self-hosted site to numericalos.com
  (Cloudflare Pages project numericalos, production branch main). Use after any
  content, docs, skills, or status change that should go live; when the user
  says "update the site", "deploy numericalos.com", "publish pages", "redeploy
  site", "sync the website", or after finishing a NumericalOS docs/status cycle
  without a git remote. Triggers: "/numericalos-publish-site", "publish site",
  "update numericalos.com", "deploy Pages", "site is stale". This is the site
  publish path — not ISO build and not boot verification.
---

# Publish NumericalOS site (self-hosted, no git remote)

This repository is **self-hosted**: there is often **no `git remote`**. Live
content reaches the world through **Cloudflare Pages** (`numericalos` project,
custom domain **https://numericalos.com**). The static generator also embeds a
dumb-HTTP clone at `/numericalos.git/` so browsers and agents can pull source
from the site itself.

**This skill is the canonical “after each update, refresh every page” path.**

It is **not**:

- `numericalos-build-iso-cf` (artifacts / R2)
- `numericalos-verify-boot` (boot claims)
- A git forge push (no remote required)

## Honesty (Tarski)

| Claim | Evidence |
|---|---|
| "Site rebuilt" | `py site/build.py` exits 0; `public/` refreshed |
| "Production Pages updated" | `wrangler pages deploy … --branch main` succeeds |
| "Live numericalos.com matches" | HTTPS probe of `/`, `/docs/status/`, or a changed path shows new content |
| "Source clone updated" | `/numericalos.git/` is regenerated inside `public/` by the build |

**Never:**

- Deploy without `--branch main` (wrong branch → preview URL only; production stale). See shadow-cf-pages-wrangler-branch-mismatch.
- Claim boot status improved because the site deployed.
- Commit or upload secrets (`.build-token`, `.dev.vars`, `node_modules/`).
- Track `public/` in git (gitignored; build output only).

## When to run

**Always run after** any of these land in the working tree and the user wants them live:

- `site/content/**`, `site/build.py`, `site/assets/**`
- `docs/**` (especially `docs/observations/`, PRINCIPLES)
- `skills/**` / `skills/AGENTS.md` (repackaged into `/skills/` and `skills.zip`)
- `README.md` when the STATUS line or agent entry points change
- `numos/**` / `boot/**` when browse tree or clone should reflect them

**Proactive:** after a multi-file NumericalOS cycle that touched status or skills,
offer or run this skill unless the user said “local only.”

## Prerequisites (probe)

```sh
# From repo root
test -f site/build.py
command -v py || command -v python3
npx wrangler --version
npx wrangler whoami
```

Record:

- `REPO` absolute path
- Whether `wrangler whoami` works (required for deploy)
- Optional: `NUMOS_OUT` override for locked `public/` on Windows

## Step 1 — preflight content hygiene

1. Confirm STATUS dual-claim still continuous if you edited it (`tests/test_principles.py`).
2. Prefer green unit tests when Python/state/status changed:

```sh
cd "$REPO"
py -3 -m unittest tests.test_principles -q
# broader if you touched distro/boot:
# py -3 -m unittest discover -s tests -q
```

3. Do **not** block site publish solely on QEMU boot tests — site ≠ boot.

## Step 2 — build every surface

From **repo root** (not `site/`):

```sh
cd "$REPO"
# Windows: py -3 site/build.py
# POSIX:   python3 site/build.py
py -3 site/build.py
```

If Windows holds locks under `public/`:

```sh
# PowerShell
$env:NUMOS_OUT = "$REPO\public-publish"
py -3 site/build.py
```

Expect: tree under `public/` (or `NUMOS_OUT`) with at least:

| Path | Role |
|---|---|
| `index.html` | home |
| `docs/status/` | evidence inventory |
| `docs/`, `whitepaper/`, `principles/`, `skills/` | content |
| `browse/` | source browser |
| `numericalos.git/` | dumb-HTTP clone |
| `index.json`, `llms.txt`, `AGENTS.md`, `skills.zip` | agent discovery |

If build fails, **stop** — do not deploy a partial tree.

## Step 3 — deploy production Pages

**Production branch must be `main`:**

```sh
cd "$REPO"
# If NUMOS_OUT was set, deploy that directory instead of public
npx wrangler pages deploy public \
  --project-name numericalos \
  --branch main \
  --commit-dirty=true
```

PowerShell one-liner (preferred helper):

```powershell
cd $REPO
.\scripts\publish-site.ps1
# or: .\scripts\publish-site.ps1 -SkipTests
```

POSIX:

```sh
./scripts/publish-site.sh
```

Capture:

- Deploy URL (e.g. `https://<hash>.numericalos.pages.dev`)
- Project name `numericalos`
- Branch `main`

## Step 4 — verify live (all key pages)

Probe **canonical host** and, if useful, the deploy preview:

```sh
# Canonical
curl -sS -o /dev/null -w "%{http_code} %{url_effective}\n" https://numericalos.com/
curl -sS -o /dev/null -w "%{http_code}\n" https://numericalos.com/docs/status/
curl -sS -o /dev/null -w "%{http_code}\n" https://numericalos.com/skills/
curl -sS -o /dev/null -w "%{http_code}\n" https://numericalos.com/whitepaper/
curl -sS -o /dev/null -w "%{http_code}\n" https://numericalos.com/principles/
curl -sS -o /dev/null -w "%{http_code}\n" https://numericalos.com/browse/
curl -sS -o /dev/null -w "%{http_code}\n" https://numericalos.com/index.json
# Spot-check a string you know you changed (example: STATUS fragment)
curl -sS https://numericalos.com/docs/status/ | head -c 2000
```

**Edge cache residual:** custom domain can lag the `*.pages.dev` deploy URL by
seconds to minutes. If preview has content and production does not yet, wait and
re-probe; do not redeploy blindly more than once.

Optional headers probe:

```sh
curl -sSI https://numericalos.com/ | grep -iE 'cf-|cache|age'
```

## Step 5 — report template

```
Mode: numericalos-publish-site (self-hosted Pages)
Repo: <path>
Build: py site/build.py → public/ (or NUMOS_OUT=…)  exit=<0>
Deploy: wrangler pages deploy --project-name numericalos --branch main
Deploy URL: https://….numericalos.pages.dev
Canonical: https://numericalos.com
Live probes:
  /              HTTP …
  /docs/status/  HTTP …  (snippet match: yes/no)
  /skills/       HTTP …
  /index.json    HTTP …
  /numericalos.git/  (optional HEAD)
Not done: git remote push (N/A when self-hosted without remote)
Not claimed: boot status (use numericalos-verify-boot)
```

## Failure modes

| Symptom | Fix |
|---|---|
| `wrangler whoami` fails | CF auth missing; stop |
| Deploy “success” but production unchanged | Missing `--branch main` |
| Build fails on locked `public/` | `NUMOS_OUT=public-publish` then deploy that dir |
| Live 200 but old HTML | Edge cache lag; re-probe deploy URL vs numericalos.com |
| Skills zip stale | Rebuild ensures `skills.zip` from `skills/`; re-run full build |
| STATUS test fails after edit | Fix continuous STATUS string before deploy |

## Automation hooks (optional)

After a successful local commit cycle on this machine:

1. Run this skill (or `scripts/publish-site.ps1`).
2. Do **not** require `git push`.
3. If the user also maintains a forge mirror later, push is a **separate** step
   and never substitutes for Pages deploy.

## Related

- `site/build.py` — generator (all pages + clone + skills package)
- `scripts/publish-site.ps1` / `scripts/publish-site.sh` — one-shot build+deploy+probe
- `skills/AGENTS.md` — multi-agent protocol (packaged onto the site)
- `numericalos-verify-boot` — boots only
- `numericalos-build-iso-cf` — ISO/R2 only
