Orchestrates building a NEW website end-to-end, from insights to launch-ready and handoff-ready. Self-contained: it runs the stack decision interview, sequences the website-* skills + existing marketing skills in order, and scaffolds the project from its own templates/ (Astro starter overlay + full QA test suite + GDPR privacy draft + owner docs + permission allowlist), copying the bundled skill set into the project's skills dir (full copy list in §3) so the repo is self-contained for a third...
Installs into .claude/skills of the current project.
Are you the author of New Website?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/karero-new-website)
---
name: new-website
description: >
Orchestrates building a NEW website end-to-end, from insights to launch-ready
and handoff-ready. Self-contained: it runs the stack decision interview,
sequences the website-* skills + existing marketing skills in order, and
scaffolds the project from its own templates/ (Astro starter overlay + full
QA test suite + GDPR privacy draft + owner docs + permission allowlist),
copying the bundled skill set into the project's skills dir (full copy list
in §3) so the repo is self-contained for a third party. Default stack
Astro → GitHub → Cloudflare Pages. Use at the very start of any new site.
Trigger phrases: "new website", "start a new site", "scaffold a website",
"spin up a site", "build a new website", "set up a new web project",
"website starter", "which stack for this site".
---
# New website — orchestrator
The entry point for every new site you build. It **decides the stack**,
**sequences the work**, and **scaffolds a self-contained, handoff-ready repo** from
this skill's `templates/`. Concrete content/SEO/design/QA work is delegated to seven
sibling skills + existing global skills.
Everything needed is bundled here:
- `templates/astro/` — the Astro starter overlay (Base.astro SEO+theme spine,
`config.ts`, theme tokens, `astro.config`, `tests/` suite, CI, preview-noindex
function, headers, llms.txt, GDPR privacy page draft). Its `README.md` is the
assembly manual.
- `templates/SETUP.md` — accounts (GitHub + Cloudflare) + tools + the bootstrap.
- `templates/PUBLISHING.md` — plain-English "how to publish" for the owner
(`commit`/`push`/`branch` explained + step-by-step per publish model), plus the
assistant-facing **deploy-time guardrails** (§4).
- `templates/AGENTS.md` + `templates/CLAUDE.md` — the working rules every assistant
(Codex, Claude Code) follows in the repo: fetch the latest state first, pull request
instead of a direct push, when a merge is allowed, never invent facts, the new-page
checklist. `CLAUDE.md` is one line (`@AGENTS.md`), so both tools read the same rules.
- `templates/.gitignore`, `templates/claude/settings.json`, `templates/claude/hooks/git-stand.mjs`
— git ignore, the permission allowlist, and the Claude Code hook that fetches from
GitHub at session start (and again after 2 hours) and reports what is new, so
`AGENTS.md` §1 happens even when the assistant skips it. The settings register the
hook: copy both, or every session start fails to find the script.
- `templates/positioning.md`, `templates/content-guide.md`, `templates/brand.md` — the per-site docs.
- `references/WEBSITE_ARCHITECTURE.md` (bundled with this skill) — the Cloudflare
**tier 1/2/3** decision tree + limits (the tiers are also summarized in §1, question 2).
Sibling skills (run in order, each usable on its own):
`website-positioning` · `website-content-guide` · `website-seo-geo` ·
`website-design-system` · `website-testimonials` · `website-qa` ·
`website-review`. `website-positioning-check` is an optional, read-only one-screen
diagnostic for a site that already has pages — run it after the build, or whenever the
offer starts to feel blurred; it is not a pipeline gate. Plus `outgoing-link-audit` — the
pre-launch / monthly external-link
liveness sweep (only relevant once the site links out); `internal-link-audit` — the
internal-linking sweep that finds orphaned + thin pages and suggests where to cross-link
(run after adding a batch of pages); `website-permissions` — install + safely extend the
repo's permission allowlist (fewer prompts, same guardrails); `search-console-setup`
— post-launch GSC + Bing registration + IndexNow; and `website-motion` — an OPTIONAL
polish layer (stat count-up + section-heading reveal) for sites with a long scrolling
homepage, plus the reduced-motion contract and the a11y-gate change that keep motion
from silently hiding content. `website-motion` is never run by default: ask for it.
`website-story` is an OPTIONAL story layer (the home page told as the customer's story:
`STORY.md` + a seven-section home page map); it is offered once at §2a, right after
positioning, and never runs unasked. `website-scorecard` is an OPTIONAL public scorecard
(a section that shows what the site's own test gate checked, dated, plus the owner's
Lighthouse scores); it runs only when the owner asks for it.
`website-team-setup` is the other on-demand skill — run it once when a **second person**
joins the repo (invite collaborators, repo settings, prove CI triggers, block direct
pushes to `main`, connect Cloudflare Pages without the known traps, set the rights level
in `AGENTS.md`); a single owner never needs it.
## 1. Decision interview (answer before any code)
**Conduct the interview — and ALL user-facing guidance throughout this pipeline
(explanations, the §3a link-sweep question, the §4 preview-vs-live announcements,
error walk-throughs) — in the language the user writes in.** A German owner gets
the whole journey in German; the skill files themselves stay English. The
verbatim message templates below are content specs, not required English wording.
Default house stack: **Astro (static) → GitHub → Cloudflare Pages**. Six questions
decide everything downstream — ask them in order, offer the examples so a
non-expert can answer, and record the answers in the project `README.md`.
1. **Pages + content types — what does the sitemap look like?**
*Decides: plain pages vs Content Collections.*
- A handful of fixed pages (home, services, about, contact, imprint — ≤ ~15)
→ plain `.astro` pages. Example: a small-business brochure site.
- Anything that repeats with the same shape — blog posts, events, case
studies, team members → Astro **Content Collections** (one schema, one
template, n entries) + the `Base` layout. Example: an event archive where
every event has date/speaker/location.
- Mixing both is normal: flat pages + one collection.
2. **Any dynamic/backend behaviour — what must a server actually do?**
*Decides: the Cloudflare tier. Pick the LOWEST tier that fits — going higher
is the classic mistake. Tier tree + limits: `references/WEBSITE_ARCHITECTURE.md`.*
- Visitors only read; contact is `mailto:` or a form service → **Tier 1
static** (~90% of sites).
- Exactly one small server task — a form that emails you, site search,
hiding a third-party API key, one live widget (e.g. a next-event box fed
by an API) → **Tier 2** (one Pages Function or server island). A contact
form that emails the owner: run **`website-forms`** (it checks first
whether the site meets what Cloudflare's email sending needs).
- State per user — accounts/login, a database, checkout, user-generated
content → **Tier 3** (SSR + D1). Rare; challenge the requirement first.
3. **Who edits content after launch?**
*Decides: CMS or not.*
- The owner/Claude editing files in git → **no CMS** (default; markdown +
redeploy is the workflow).
- A non-technical person (client, co-organizer writing posts) →
**Keystatic** (git-based, no separate backend). Can be added later —
don't install it speculatively. When chosen, run **`keystatic-setup`**
(local mode; the GitHub cloud-mode upgrade is documented there).
4. **One language or several?**
*Decides: i18n routing from day 1 or never.*
- One language (German-only counts) → skip i18n entirely.
- Two+ **at launch** (e.g. a DE+EN consultancy site) → run
**`astro-i18n-setup`** (Astro i18n: clean default locale + prefixed others,
self-referencing hreflang + `x-default`, sitemap alternates, language switcher,
per-locale test harness).
- **Multilingual, but building one language first?** Fine — keep the default locale
**unprefixed** from day 1, ship/test the primary language, then run
**`astro-i18n-setup`** to add the second locale when translations are ready. Because
the default stays at `/`, that's additive, not a breaking retrofit (skill →
*Phased rollout*).
- "Maybe English someday" → treat as one language now; the unprefixed default keeps a
future second locale additive. **The only costly change is later moving the default
off `/`** (e.g. `/` → `/en/`).
4a. **What language should the content actually be written in?**
*Decides: the copy language — a separate decision from Q4's routing question above.*
Answering "one language" in Q4 does **not** mean English. The starter defaults to
English (`templates/astro/src/config.ts` ships `locale: 'en'`, and the placeholder
homepage copy is English prose) — say so explicitly if you want German, or another
language, instead. **German-only is a completely valid, common answer** here, not
just "English, obviously, unless multilingual."
5. **Analytics — will anyone act on the numbers? (optional)**
- Just "is anyone visiting?" → **Google Search Console** alone (free, no
script, no consent banner) is enough for search & referral traffic.
- Real decisions from traffic data (campaigns, content strategy) →
**Plausible** (cookieless, privacy-friendly, no consent banner needed).
If you run a **self-hosted** Plausible instance, point at it; otherwise
recommend the **paid cloud version** (from **€9/mo** —
https://plausible.io/#pricing) so the client runs no server.
- Prefer cookieless tools over Google Analytics, which would pull in a
cookie-consent banner — for German-market sites the statute behind that
banner is § 25 TDDDG (formerly TTDSG). Third-party additions kill the
banner-free claim by different routes: a YouTube iframe triggers § 25
itself (device storage/readout), while remote Google Fonts flip the
answer via the GDPR instead (unconsented IP disclosure — LG München I,
3 O 17493/20; self-host fonts, as this starter does). Any analytics script is gated to the production branch only. The
privacy page must match whatever is chosen here.
6. **Domain + how do changes go live? (publish model)**
*Decides: the publish workflow — a safety-vs-simplicity trade-off. **Offer both, explain
them, let the owner choose**; recommend two-stage. Record the choice in the README.*
- New domain or subdomain of an existing site? (Affects canonical URLs and whether
existing SEO authority carries over.) DNS moves to Cloudflare.
- **Two-stage — recommended (default).** `main` is an **unlisted, noindexed preview**
(`*.pages.dev` — noindexed, but public-by-URL, not access-controlled); `production` is
the **live** site. The owner pushes to `main`, checks
the preview link, then runs **`npm run ship`** to publish. A mistake never reaches the
live domain. *Scaffold adds:* the `production` branch, the `_middleware.ts` noindex
guard, and the `ship` script.
- **Single-stage — simplest.** One branch — `main` is live, so every push goes straight
to the public site. Fewer moving parts, no safety net. Pick this only for a low-stakes
site whose owner is fine with "save = instantly public." **Note:** the `_middleware.ts`
noindex guard de-indexes every `*.pages.dev` host, so a single-stage site is invisible
to Google until a **custom domain** is attached — say this when recommending it.
- **Don't assume git fluency.** Whichever they choose, hand them
`templates/PUBLISHING.md` (plain-English `commit` / `push` / `branch` + the exact
step-by-step to publish) and walk the **first** publish through with them.
Plain hand-written HTML (static `.html` files, no build step) is a legacy anti-pattern — use Astro.
## 2. The pipeline (the order to use the skills)
| # | Step | Skill / source |
|---|---|---|
| 1 | Insights: ICP, voice-of-customer, competitor scan | `customer-research` |
| 2 | Positioning: what you offer, for whom, market category → `POSITIONING.md` (Dunford) | **`website-positioning`** |
| 2a | Optional: the home page as the customer's story → `STORY.md` (offered once, §2a; positioning stays the source of truth) | `website-story` |
| 3 | Tone of Voice, EEAT, page inventory → `CONTENT_GUIDE.md` + `BRAND.md` | **`website-content-guide`** |
| 4 | Pages, clean URLs, nav, internal links | `site-architecture` |
| 5 | Decision interview + scaffold the repo | **this skill** §1, §3 |
| 6 | Build: mobile graphics, dark mode, theme tokens | **`website-design-system`** |
| 6 | Build: head metadata within limits, schema, llms.txt | **`website-seo-geo`** (+ `schema-markup`, `ai-seo`) |
| 6 | Build: per-page OG share cards (`npm run og` → 1200×630 ≤300 KB, tested) | **`og-images`** |
| 6 | Build: testimonials / Review schema from one data file (if the site has quotes) | **`website-testimonials`** |
| 7 | QA: a11y (light+dark) / seo / navigation / anchors / orphans / images / tone / positioning / placeholders / email / links | **`website-qa`** |
| 8 | Performance: Lighthouse / PageSpeed for FCP & LCP | **`website-qa`** perf section (+ `seo-audit`) |
| 9 | **Double-Knuth review** (correctness + cross-file consistency) | **`website-review`** |
| 10 | Outgoing-link liveness sweep — **only if the site links out** (see §3a) | **`outgoing-link-audit`** |
| 10b | Internal-link sweep — orphaned + thin pages (run if the site grew past a handful of pages) | **`internal-link-audit`** |
| 11 | Launch & handoff: schema/sitemap/robots, deploy, hand over | **this skill** §4 |
| 12 | Post-launch: register with Google Search Console + Bing, submit sitemap, enable IndexNow | **`search-console-setup`** |
| 13 | Post-launch: claim Google Business Profile + Bing Places, verify `sameAs` profiles resolve — **only if the site is a claimable entity** (see §4a) | **`business-listings-setup`** |
Work out **positioning** (step 2) *before* any content or SEO — it decides what the
copy is even trying to say. Build the Content Guide (step 3) *before* writing page
copy; positioning and voice are inputs to everything downstream, not an afterthought.
The build is **test-driven, not test-after**: the suite is green from commit 1,
and steps 6–7 run as a loop per page (red → green → commit, see §3 step 5 and
`website-qa` §1b). Step 7 in the table is the *final full-suite gate*, not the
first time tests run.
## 2a. Story layer — offer once, right after positioning
`website-story` is optional and the owner has usually never heard of it, so explain
before asking. Once `POSITIONING.md` is filled (step 2) and before the content guide
(step 3):
1. Read **"The offer"** in `website-story/SKILL.md` and say it in the language the
owner writes in (it explains the idea in plain words: the visitor as the hero, the
brand as the guide, a one-liner, a three-step plan, one repeated call to action,
positioning unchanged). Do not paraphrase it into jargon.
2. Ask once — a structured user-input tool if the platform offers one (Claude Code's
**`AskUserQuestion`**), otherwise in chat — with two options: **Yes, build the home
page as a story** / **No, standard home page (default)**.
3. **Yes** → run `website-story` now, before step 3, so `CONTENT_GUIDE.md`'s home row
and `copywriting` read `STORY.md`.
4. **No, or no answer** → say nothing more about it. The skill still travels with the
repo (§3 step 3), so it can be run later whenever the owner asks.
5. **Record the answer either way** ("home page: story-led, see STORY.md" or "home page:
standard, story layer declined") with the decision-interview answers when §3 writes
the project README, so a later session knows the offer was made and does not ask
again.
Ask exactly once per build. Never run it unasked.
## 3. Scaffold the project (handoff-ready)
**Prerequisites (one-time).** Walk the user through `templates/SETUP.md` if needed —
Node/git/gh/wrangler + image tools, a **private GitHub** account, a **Cloudflare**
account. The accounts are a human action the agent cannot do. When the owner sets up
GitHub, tell them plainly that the account and the website in it are theirs, and recommend
two-factor sign-in with a passkey to keep others out (`SETUP.md` §1 has the link); for
Cloudflare, which decides whether the site is online, the same with a security key or the
device's fingerprint or face unlock. Recommend, never require: GitHub makes it mandatory only
for some accounts, and Cloudflare leaves it optional unless an account's administrator
enforces it for members.
Assemble the project at `<site>/` so it travels without any global setup:
0. **Git first, before any code:**
```bash
# Resolve where the suite is installed (the cp SOURCE). Per tool:
# ~/.claude/skills = Claude Code · ~/.agents/skills = Codex · ~/.gemini/config/skills = Antigravity
# Honour an explicit $SKILLS_ROOT; else auto-detect in priority order
# Claude Code → Antigravity → Codex (Claude is the default/primary). On a machine with
# more than one installed, set it yourself — e.g. `export SKILLS_ROOT=~/.agents/skills`
# (Codex) or `~/.gemini/config/skills` (Antigravity); Antigravity workspace installs use
# `export SKILLS_ROOT="$PWD/.agents/skills"`.
if [ -z "${SKILLS_ROOT:-}" ]; then
SKILLS_ROOT="$HOME/.claude/skills"
for d in "$HOME/.claude/skills" "$HOME/.gemini/config/skills" "$HOME/.agents/skills"; do
[ -d "$d/new-website" ] && SKILLS_ROOT="$d" && break
done
fi
# Where bundled skills go IN the generated project (the cp DESTINATION). Claude default
# (.claude/skills). Codex and Antigravity read repo-scoped skills from .agents/skills, so
# derive that when the suite was installed through either of them. Force it with:
# export PROJECT_SKILLS_DIR=.agents/skills
if [ -z "${PROJECT_SKILLS_DIR:-}" ]; then
case "$SKILLS_ROOT" in
"$HOME/.agents/skills"*|"$HOME/.gemini/config/skills"*|*/.agents/skills*) PROJECT_SKILLS_DIR=".agents/skills" ;;
*) PROJECT_SKILLS_DIR=".claude/skills" ;;
esac
fi
mkdir <site> && cd <site> && git init
cp "$SKILLS_ROOT"/new-website/templates/.gitignore .
git add .gitignore && git commit -m "chore: init repo with .gitignore"
```
1. **Scaffold + overlay:** `npm create astro@latest .` (Empty, TS strict), then copy
the `templates/astro/` overlay (`src/`, `tests/`, `public/`, `functions/`,
`scripts/`, `.github/`, `.nvmrc`, `.gitattributes`, root configs — see `templates/astro/README.md`
for exact steps and npm deps). Set the real domain in `astro.config.mjs` (`site:`)
and `src/config.ts`. **Set `SITE.locale` in `src/config.ts` to match the interview's
Q4a content-language answer** (and `lang` in `Base.astro` too, if not running
`astro-i18n-setup`). **`'de'` flips the footer's privacy link to `/datenschutz`** —
do the privacy→datenschutz swap in the SAME step (rename the in-repo
draft `src/pages/_datenschutz.astro` per its header: page file,
`tests/_helpers.ts` PAGES, `public/llms.txt`, `OWN_CARD_EXEMPT`,
`POSITIONING_EXEMPT` + `UNFILLED_UNTIL_LAUNCH` in the specs) or every
intermediate `npm test` fails navigation on the footer link.
The overlay ships `locale: 'en'`, and leaving that default in
place for a German-content (or other non-English) site is exactly the silent-default
bug Q4a exists to catch. **Astro needs Node ≥22.12** — the overlay's `.nvmrc` pins
24 for local + Cloudflare Pages builds.
2. **Permissions + working rules.** Copy the setup guide into the project (all tools), then — **Claude Code
only** — copy the allowlist so routine `npm`/`astro`/`playwright`/`git commit` calls don't
prompt (it still asks for `rm -rf`, `git push --force`, `wrangler … delete`, `gh repo delete`):
```bash
cp "$SKILLS_ROOT"/new-website/templates/SETUP.md . # all tools — receiving party can set up too
cp "$SKILLS_ROOT"/new-website/templates/PUBLISHING.md . # owner "how to publish" + assistant deploy guardrails
cp "$SKILLS_ROOT"/new-website/templates/AGENTS.md . # working rules for every assistant (Codex + Claude)
cp "$SKILLS_ROOT"/new-website/templates/CLAUDE.md . # one line: @AGENTS.md
# Claude Code only:
mkdir -p .claude/hooks
cp "$SKILLS_ROOT"/new-website/templates/claude/settings.json .claude/settings.json
cp "$SKILLS_ROOT"/new-website/templates/claude/hooks/git-stand.mjs .claude/hooks/ # sync hook the settings register
```
*Codex / Antigravity: skip the `.claude/settings.json` copy — it's Claude Code-specific.
Use their own approval systems instead (Codex: `AGENTS.md` + Codex rules/config;
Antigravity: its sandbox approval model).* For Claude's allow/deny model and how to extend
it safely when a prompt keeps recurring, use **`website-permissions`**.
**Fill `AGENTS.md` now**, per the scaffold note at its top: site name, live URL, the
preview URL (two-stage: `main.<project>.pages.dev`; single-stage: "pull-request
previews only, `<branch>.<project>.pages.dev`" — `main` is live there; update the
project name if Cloudflare later forces another one), `[TITLE_SUFFIX]` = the
` | {SITE.name}` string `Base.astro` appends, `[SUFFIX_LENGTH]` = its length, and
`[TITLE_MAX]` = 60 minus that length; keep ONE publish-model block in its §2 (the
interview's Q6 answer) and delete the other. §5 (collaborators, rights level, merge
rule, who publishes) ships with single-owner defaults, not slots; `website-team-setup`
rewrites it when a team forms. `AGENTS.md` stays English for every owner, like the
skills: agents read it, and its Language rule has them reply in the person's
language. Only the human-facing `PUBLISHING.md` gets translated (see §4).
3. **Skills travel with the repo** — copy the twenty-five always-on skills in, plus any
conditional setup skills selected by the interview, so the handoffs resolve for the
receiving party. "Always-on" here means always **copied** into the project, not
necessarily always **run**: `business-listings-setup` travels with every repo but
only executes when §4a's gate says the site is a claimable entity — it still needs
to be in the repo so a later session can run it once that's true. The always-on set is the
seven core `website-*` siblings, `website-positioning-check` (always copied, optional to run),
the three SEO-depth skills they delegate to — `ai-seo`, `schema-markup`,
`seo-audit` — `site-architecture` (IA), the three
marketing skills the pipeline delegates to — `customer-research`, `copywriting`,
`image` — plus `outgoing-link-audit` (external link sweep), `internal-link-audit`
(orphan/thin-page sweep), `og-images` (per-page share cards),
`website-permissions` (allowlist),
`search-console-setup` (post-launch GSC/Bing/IndexNow),
`business-listings-setup` (post-launch Business Profile/Bing Places/
`sameAs` — gated per §4a), `website-motion` (optional polish — copied so
the recipient can opt in later; it never runs on its own), `website-story`
(optional story layer — same rule: copied, never runs unasked; offered once at §2a),
`website-scorecard` (optional public scorecard — same rule: copied, never runs
unasked), and `website-team-setup` (copied so the day a second person joins, the session that
sets up the team finds it; it never runs on its own either):
`$SKILLS_ROOT` entries are often symlinks (e.g. a `make install` checkout
symlinks each skill from this suite repo) — use `cp -RL` to dereference
them, not `cp -R`, or the copy ships broken symlinks pointing back at the
developer's own machine instead of a self-contained skill directory:
```bash
mkdir -p "$PROJECT_SKILLS_DIR"
cp -RL "$SKILLS_ROOT"/website-positioning \
"$SKILLS_ROOT"/website-positioning-check \
"$SKILLS_ROOT"/website-content-guide \
"$SKILLS_ROOT"/website-seo-geo \
"$SKILLS_ROOT"/website-design-system \
"$SKILLS_ROOT"/website-testimonials \
"$SKILLS_ROOT"/website-qa \
"$SKILLS_ROOT"/website-review \
"$SKILLS_ROOT"/ai-seo \
"$SKILLS_ROOT"/schema-markup \
"$SKILLS_ROOT"/seo-audit \
"$SKILLS_ROOT"/site-architecture \
"$SKILLS_ROOT"/customer-research \
"$SKILLS_ROOT"/copywriting \
"$SKILLS_ROOT"/image \
"$SKILLS_ROOT"/outgoing-link-audit \
"$SKILLS_ROOT"/internal-link-audit \
"$SKILLS_ROOT"/og-images \
"$SKILLS_ROOT"/website-permissions \
"$SKILLS_ROOT"/search-console-setup \
"$SKILLS_ROOT"/business-listings-setup \
"$SKILLS_ROOT"/website-motion \
"$SKILLS_ROOT"/website-story \
"$SKILLS_ROOT"/website-scorecard \
"$SKILLS_ROOT"/website-team-setup \
"$PROJECT_SKILLS_DIR"/
```
The global copies stay the updateable source of truth; the project copies are
the frozen handoff set.
**Conditional setup skills** — run the matching line ONLY when the interview
selected it (they don't ship with a declared one-language, CMS-free site without a form;
a multilingual-PHASED site is single-locale at scaffold time and still
gets astro-i18n-setup):
```bash
# If Q2 = "a form that emails you" (a contact form):
cp -RL "$SKILLS_ROOT"/website-forms "$PROJECT_SKILLS_DIR"/
# If Q3 = "non-technical editor" (Keystatic):
cp -RL "$SKILLS_ROOT"/keystatic-setup "$PROJECT_SKILLS_DIR"/
# If Q4 = "2+ languages at launch" OR "multilingual, one language first"
# (phased rollout): the phased site NEEDS the skill vendored from day 1 —
# its Phase 2 instruction is "run astro-i18n-setup when translations are
# ready", which can't resolve if the skill was never copied.
cp -RL "$SKILLS_ROOT"/astro-i18n-setup "$PROJECT_SKILLS_DIR"/
```
**Sanity check** (always, once all copying above — the primary batch AND
any conditional skills — is complete) — confirm nothing was copied as a
symlink:
```bash
find "$PROJECT_SKILLS_DIR" -maxdepth 1 -type l
# Must print nothing. Any output means a skill copied as a symlink, not a
# real directory — the handoff repo would ship broken links back to this
# machine. Re-run the copy for the listed name(s) with cp -RL.
```
**Stamp the copies** (always, after ALL skills are copied) — records which suite
state they came from, so `make whats-new PROJECT=<site>` in the suite repo can later
report which bundled skills have upstream updates:
```bash
# The suite's scripts/whats-new.sh is the ONLY writer of the stamp format. Resolve
# the suite clone via the symlink and verify it really is the suite — a copied
# (non-symlink) install sitting inside some other git repo must NOT stamp that
# repo's HEAD.
SUITE_SRC="$(readlink "$SKILLS_ROOT/new-website" 2>/dev/null || echo "$SKILLS_ROOT/new-website")"
SUITE_REPO="$(git -C "$SUITE_SRC" rev-parse --show-toplevel 2>/dev/null || true)"
if [ -n "$SUITE_REPO" ] && [ -f "$SUITE_REPO/scripts/whats-new.sh" ] \
&& [ -d "$SUITE_REPO/skills/new-website" ]; then
bash "$SUITE_REPO/scripts/whats-new.sh" --stamp "$PROJECT_SKILLS_DIR"
# Also stamp the FROZEN template copies (tests/*.spec.ts, CONTENT_GUIDE.md):
# whats-new reports their upstream drift separately — --refresh never touches
# them, they're merged by hand (they may carry site edits like the PAGES list).
bash "$SUITE_REPO/scripts/whats-new.sh" --stamp-tests tests
else
# No usable suite clone (zip/copy install): record that the baseline is unknown.
# whats-new can't report for this project until re-stamped from a real clone.
printf 'suite_commit: unknown\ncopied: %s\n' "$(date +%Y-%m-%d)" \
> "$PROJECT_SKILLS_DIR"/SUITE-VERSION
fi
```
4. **Docs** — copy `templates/positioning.md` → `POSITIONING.md`,
`templates/content-guide.md` → `CONTENT_GUIDE.md` and `templates/brand.md` →
`BRAND.md`; fill the `[BRACKET]` slots in pipeline steps 2–3. `STORY.md` exists
only if the owner opted in at §2a; `website-story` copies it from its own
`templates/story.md`.
5. **Confirm green:** `npm run build && npm test` (the overlay passes the
a11y/seo/navigation/anchors/orphans/images/tone/positioning/placeholders/email/links/llms-coverage/middleware suite out of the box). Then build pages
test-first: add the route to `tests/_helpers.ts` `PAGES` *before* writing the
page (suite goes red), build until green, commit. New features get their test
first too — `website-qa` §1b maps feature → test.
## 3a. Outgoing-link sweep — ask, but only if the site links out
The offline `tests/links.spec.ts` guard ships in the suite and runs in CI from commit
1 — nothing to decide there. The **liveness** sweep (`outgoing-link-audit`) is
network-dependent and optional, so gate it on whether the built site actually has
external links, and let the user choose:
```bash
cd "$(git rev-parse --show-toplevel)"
[ -d dist ] || npm run build >/dev/null
SITE_HOST=$(grep -oE "site:[[:space:]]*['\"]https?://[^'\"]+" astro.config.mjs \
| sed -E "s#.*://(www\.)?##; s#/.*##" | head -1)
EXT=$(grep -rhoE '<a [^>]*href="https?://[^"]+"' dist --include='*.html' \
| grep -oE 'href="https?://[^"]+"' | sed -E 's/^href="//; s/"$//' \
| grep -vE "://(www\.)?${SITE_HOST}(/|$|:)" | sort -u | wc -l | tr -d ' ')
```
- **`EXT` is 0** → the site links only to itself. Say so and **skip silently** — do
not ask.
- **`EXT` ≥ 1** → ask whether to run the liveness sweep now — use a structured
user-input tool if the platform offers one (Claude Code's **`AskUserQuestion`**),
otherwise just ask in chat — e.g. *"The site has N outgoing links. Run the `outgoing-link-audit` liveness
sweep before launch? (fetches each third-party URL; ~a few seconds each)"* — options
**Run it now (recommended)** / **Skip — I'll run it monthly**. If they say run it,
invoke the `outgoing-link-audit` skill; otherwise note it as a monthly follow-up.
## 4. Launch & handoff checklist
### Deploy-time guardrails — `templates/PUBLISHING.md` § "For AI assistants — deploy-time guardrails"
That section is the single source of truth — §3 copies `PUBLISHING.md` into every site, so
the post-handoff agent carries it too (the scaffolded README's deploy section points there).
Read it in full before the FIRST deploy, when a brand-new page goes live, and when a live
URL looks stale; this summary covers routine pushes.
**Always announce whether a push is PREVIEW or LIVE** (two-stage sites: "this
is NOT live yet" + the preview URL on every push to `main`; live only after
`npm run ship` AND the production build finishes). **Never request — or
announce — a brand-new page's URL on the live domain before its build is
Active**: the first request — the owner clicking your announcement counts —
caches a 404 at the edge that only a manual dashboard purge clears. Verify on
the hash deployment URL until Active, touch the bare canonical URL last, and
hold Search Console Request Indexing until then.
- [ ] All QA green: `npm test`, with **no "not ready to launch" warning**:
`UNFILLED_UNTIL_LAUNCH` in `tests/placeholders.spec.ts` is empty. The starter
lists the privacy page, the imprint and the manifest there until their
`[BRACKET]` slots are filled; each entry comes out when its target is filled
(the test turns red until it does). An entry still listed at launch ships
that placeholder: the test only warns about a listed target. And an empty
list is not the whole check: the spec has no rule for the starter's example
values, so read the home page and both legal pages once for the name
`Example` and the address `hello@example.com`, and `public/llms.txt` for its
own `[bracket]` slots.
- [ ] **Nothing left unshipped** (two-stage sites): `git log origin/production..origin/main`
is empty — merged-but-unpromoted work is invisibly unshipped. If not empty and it
should ship: `npm run ship` (which also VERIFIES the live site serves the new build
via `/build.txt` — push alone is not proof of live).
- [ ] **Double-Knuth review clean** (`website-review`): both passes — run it as the final
gate AND after adding the last page.
- [ ] Lighthouse / PageSpeed run; FCP & LCP within target (see `website-qa`).
- [ ] **Outgoing links healthy** (`outgoing-link-audit`) — only if the site links out
(§3a). No DEAD/REBRAND left unhandled; any retired domain added to
`tests/links.spec.ts` `STALE_DOMAINS`.
- [ ] **No orphaned pages** — `tests/orphans.spec.ts` green (every page reachable from
home). For a multi-page site, run `internal-link-audit` and resolve orphans + thin
pages; deliberately-unlinked pages listed in `ORPHAN_EXEMPT` with a reason.
- [ ] `sitemap-index.xml` present; `robots.txt` + `llms.txt` accurate. (llms.txt
*coverage* — every `PAGES` route listed, no stale entry for a removed or
renamed page — is enforced both ways by `tests/llms-coverage.spec.ts`;
wording accuracy stays a human check.)
- [ ] Required schema validates (Rich Results / schema.org validator).
- [ ] **OG share cards** generated (`npm run og`, the `og-images` skill): a 1200×630
JPEG **≤300 KB** per page (`public/images/og/`), each page wiring `image=` (or the
default). `tests/seo.spec.ts` enforces size/dimensions; WhatsApp drops previews over
~300 KB. Plus favicon/manifest icon set in place, and the manifest's
`[BRACKET]` fields (name/short_name/description) filled in the site's
content language with `lang` set from `SITE.locale` — `placeholders.spec.ts`
catches a bracket left there, but no spec reads the manifest's language, so
English in a German site's manifest ships silently otherwise.
- [ ] Imprint/legal + privacy pages present (EEAT trust + DE legal requirement).
The starter ships a GDPR privacy draft (`src/pages/privacy.astro`): every
`[BRACKET]` slot filled, the analytics section matching the real setup.
German-market sites: don't re-translate — swap in the vetted German
drafts that ship in the site repo, whose own file headers carry the
exact steps: `src/pages/_datenschutz.astro` (§ 25 TDDDG-aware;
underscore = unrouted until renamed; German-only sites serve it at
`/datenschutz` REPLACING `/privacy`, multilingual sites use its text at
`/de/privacy`) and `src/pages/impressum.astro` (§ 5 DDG + § 18 Abs. 2
MStV — required for providers established in Germany, whatever the
site's language, and for sites targeting the German market; fill/delete
per its header and section comments, which also cover Austrian/Swiss
adaptation and the six-piece removal for providers with no German
nexus).
- [ ] Deployed to Cloudflare Pages per the chosen **publish model** (§1 Q6).
**Two-stage:** create the live branch (`git checkout -b production && git push -u
origin production && git checkout main` — end back on `main`), then in Cloudflare set
*Production branch* = `production` — so
`main` = noindexed preview, `production` = live; analytics fires on production only.
**Single-stage:** `main` = live (default Cloudflare production branch).
**Creating the Pages project + first deploy** (the step a Cloudflare newcomer
struggles with) — offer the bootstrap options in
`references/CLOUDFLARE_FIRST_DEPLOY.md`: recommend the **token-assisted** path for
owners new to Cloudflare (least-privilege, expiring token; you run Wrangler), with
git-integration (push-to-deploy, no token) and browser/manual as the other offered paths.
Either way: hand the owner `PUBLISHING.md` and walk the **first** publish with them.
Non-English-speaking owner? Translate `PUBLISHING.md` in-session first (e.g. save
as `PUBLISHING.de.md`, or replace the copy) — it is the owner's PERMANENT reference,
unlike your conversational guidance, and the shipped template is English. Any
translation or replacement MUST keep the "For AI assistants — deploy-time
guardrails" section (translated is fine, dropped is not — it is the post-handoff
agent's only copy of those rules).
- [ ] **`<project>.pages.dev` redirects to the live domain**: once the live domain serves
this build, Production variable `CANONICAL_URL` set and redeployed; `curl -sI` on the
alias shows `301` (`references/CLOUDFLARE_FIRST_DEPLOY.md`, "After go-live").
- [ ] **Search engines notified** (`search-console-setup`): live domain added to Google
Search Console (Domain property + DNS TXT) and Bing (import from GSC),
`sitemap-index.xml` submitted to both, and **IndexNow on** (Cloudflare Crawler
Hints toggle). Register the production domain only — never a preview host.
- [ ] **Business listings claimed, if applicable** (`business-listings-setup`,
§4a): `sameAs` verification done for any named entity — every URL in
schema confirmed live or manually verified logged-out, per that skill's
own verification rules; directories claimed where a suitable one exists
for the category; Google Business Profile and Bing Places live only if
the entity actually qualifies (the skill's §1 step 0 checks). "No
relevant directory found" and "not eligible" are valid, non-blocking
outcomes — distinct from "skipped by owner choice".
- [ ] Repo self-contained for the receiving party: `.gitignore`, `.claude/`,
`POSITIONING.md`, `CONTENT_GUIDE.md`, `BRAND.md` (+ `STORY.md` if opted in), `tests/`, `SETUP.md`,
`PUBLISHING.md` (with its "For AI assistants" guardrails section intact),
`AGENTS.md` + `CLAUDE.md` (no `[BRACKET]` slot left, the scaffold note removed,
one publish-model block kept in §2), and a
`README.md` with the decision answers + "how to add a page / run tests / deploy".
## 4a. Business listings — ask, but only if the site is a claimable entity
`business-listings-setup` claims category directories and verifies the site's
`sameAs` schema resolves — both apply to any named entity. Google Business
Profile and Bing Places are narrower: they need a real address customers visit,
or staff who travel to serve customers, which the skill's own §1 checks. Naming
the entity is enough to run the skill; it is not enough to guarantee a Google/Bing
profile will follow, and the ask below says so. Gate it the same way as the
outgoing-link sweep in §3a: check a condition, then let the user choose rather
than assuming.
- **The site has no claimable identity at all** (a personal blog with no
business or professional practice behind it, a resource hub with no identity
of its own) → there is nothing to claim. Say so and **skip silently** — do not
ask. A sole proprietor or consultant who trades under their own name (a
photographer, a freelancer) still counts as a named entity here — the test is
"is there a business or practice to claim," not "is the name different from a
person's."
- **The site represents a named, real-world entity** (an event series, a studio, a
local business, a community or organization with its own name) → ask whether to
run it now — e.g. *"This looks like a named entity (`<name>`) that could claim
directory listings and verify its schema profiles. It may also qualify for a
Google Business Profile if it has a real address or serves customers in
person — the skill checks that itself. Want to set this up now? It needs a few
clicks from you at each platform — I'll draft everything else."* — options
**Run it now (recommended)** / **Skip — I'll do it later**. If they say run it,
invoke the `business-listings-setup` skill; otherwise note it as a post-launch
follow-up alongside `search-console-setup`.
## Notes
- Do not rebuild what existing skills cover — GEO depth → `ai-seo`; technical-SEO/
Core-Web-Vitals → `seo-audit`; JSON-LD → `schema-markup`; IA → `site-architecture`;
research → `customer-research`; copy → `copywriting`; images → `image`.
- After any code/copy edit, re-run `website-qa`. "Done" means the tests are green,
not that the file was written.
- Run **`website-review`** (the Double-Knuth two-pass audit) at the **end of a build**
(pre-launch) and **after adding/editing a page** — it's the cross-file consistency gate
on top of `website-qa`'s tests (a new route can silently break `PAGES`, nav, the sitemap,
internal links or canonical).
- **Editing the skill suite itself** (these templates, the test specs, or a sibling
skill) is different from reviewing a site: green tests do not catch doc↔test drift,
so run `/code-review` before trusting the change. The Double-Knuth pass that hardened
this suite found exactly that class of issue.