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-croftweaver)
---
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-tagline` is an
OPTIONAL skill for the short line under the logo (it starts from the purpose in
`POSITIONING.md`, shows the owner six to eight lines and lets them pick one →
`TAGLINE.md` + `SITE.tagline`); it too runs only when the owner asks for it.
`website-brand-kit` is OPTIONAL help with colours and a logo for an owner who is not a
designer (three prompts for the design tool she likes, then a checked install of the
palette and the seven icon files); the owner is asked once at §2b, right after
positioning and the story, and it never runs unasked.
`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.
A private repo gets 2 000 free CI minutes a month on GitHub Free (about 400 runs of the kit's
`ci.yml`); past that, jobs stop with a message that reads like a failed payment. Tell the
owner to check **Budgets and alerts** for an Actions budget (`website-team-setup` §3b has
the steps).
## 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 an email link, or a contact form that
opens the visitor's own mail program → **Tier 1 static** (~90% of sites).
A contact form: run **`website-contact-form`**, our suggestion (no server,
no account, no setting).
- Exactly one small server task — site search, hiding a third-party API
key, one live widget (e.g. a next-event box fed by an API), a form that
must send through a server → **Tier 2** (one Pages Function or server
island). No skill builds such a form.
- 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.
- "Which sources bring enquiries?" or other real decisions from traffic data
(campaigns, content strategy) → **Plausible** (cookieless; running it without
a consent banner is a position to have reviewed, see the header of `privacy.astro`
and, for a German-only site, `_datenschutz.astro`). In its installer, untick the
optional measurements (outbound links, file downloads, form submissions)
unless you keep the privacy page's slot sentence for each one you leave ticked.
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.
**The enquiry count comes with it.** Each click on an email address, and each
press of the contact form's button that passes its checks, sends one "Email Tap"
event (with the page and the referrer, as every event; no address, nothing typed).
In Plausible, under the site's Settings > Goals, add a goal of type "Custom event"
named exactly `Email Tap` **before launch**: Plausible's documentation says an
event shows only once its goal exists and earlier ones are not counted. Filtered
by that goal, the dashboard shows which sources and pages bring enquiries. It
counts visitors who asked to write an email; whether their mail program opened,
and whether they sent the mail, the site never learns.
- 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 put the
banner-free position at risk by different routes: a YouTube iframe triggers § 25
itself (device storage/readout), while remote Google Fonts change 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` |
| 2b | Optional: colours and a logo (asked once, §2b; her choices are made here, the install waits for the green baseline of §3 step 5) | `website-brand-kit` |
| 3 | Tone of Voice, EEAT, page inventory → `CONTENT_GUIDE.md` + `BRAND.md` (its palette and logo come from step 2b, where the owner brought or chose them) | **`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`** |
| 14 | Post-launch: ask once for a "Built with Croftweaver" credit in the footer — a request, never a condition (see §4b) | **this skill** §4b |
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.
## 2b. Colours and logo — ask once, after positioning and the story
`website-brand-kit` is optional. It is a plain question, not a pitch. Once
`POSITIONING.md` is filled (and §2a is answered) and before the content guide (step 3):
1. Read **"The question"** in `website-brand-kit/SKILL.md` and ask it, as it stands
there, in the language the owner writes in.
2. **She has a part and wants it used** → note the path of her logo file, which must
be an SVG, or her colours as hex values. No prompt is written for that part. Her own
colours still go through the skill's check (its §3 step 3) now.
3. **She wants help with a part** → run now, before step 3, the steps before §6 that
`website-brand-kit` §0's table names for her answer, and only those (help with
colours alone does not start the logo steps, and the other way round), so the
palette exists when `BRAND.md`'s "Brand in one line" is written. These steps are a
conversation: nothing is written into a project yet (there is none).
4. **She lacks a part and wants no help with it** → say nothing more about it. The site
keeps the starter's colours and the scaffold's placeholder icons for that part. The
skill still travels with the repo (§3 step 3).
5. **Record the answers either way** with the decision-interview answers (what she has,
what she wanted help with, the path of her own logo file or of her package, and,
where she took the help, the palette and the variation she picked), so a later
session knows the question was asked and does not ask again. Until §3 step 4 writes
them into the project they live in this conversation: do that step in the same
session, or write them into a note in the folder you are working in.
6. **The install comes later:** §3 step 5, on the green baseline.
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. `<site>` is a new
folder inside the owner's websites folder, which is the folder this session was opened in
(never that folder itself). Before step 0, unless the owner has already confirmed it in this
session, say which folder you are in and ask whether it is their websites folder. If it is
not, or it already holds a site (an `astro.config.*` or `index.html` file) or is the toolkit
folder (it has `skills/new-website/SKILL.md`), ask which folder is their websites folder
instead. Start step 0 from the websites folder, and once `<site>` exists, tell the owner its
full path (the output of `pwd` inside it):
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. First the folder this very skill was
# loaded from: in the first candidate of the loop below, Claude Code puts this skill's
# own folder where the name in braces stands, which also finds the suite when it is
# installed as a plugin and so sits in none of the three folders above. Claude Code
# replaces the name only where nothing follows it inside the braces, so the line before
# the loop reaches every assistant as written: where the name was left alone, it gives
# the shell an empty value for it instead of an unset variable, the candidate reads
# "/..", and the loop passes over it. Then 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"
: "${CLAUDE_SKILL_DIR:=}"
for d in "${CLAUDE_SKILL_DIR}/.." "$HOME/.claude/skills" "$HOME/.gemini/config/skills" "$HOME/.agents/skills"; do
[ "$d" != /.. ] && [ -d "$d/new-website" ] && SKILLS_ROOT="$(cd "$d" && pwd)" && 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-seven 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), `website-brand-kit` (optional colours and logo — same rule: copied, never
runs unasked; asked about once at §2b), `website-tagline` (optional line under the logo — 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-brand-kit \
"$SKILLS_ROOT"/website-tagline \
"$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
contact form;
a multilingual-PHASED site is single-locale at scaffold time and still
gets astro-i18n-setup):
```bash
# 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"/
# If Q2 = a contact form:
cp -RL "$SKILLS_ROOT"/website-contact-form "$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-dir>` (the site's full path) 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`. `TAGLINE.md` exists only if the owner asked for a tagline;
`website-tagline` copies it from its own `templates/tagline.md`. If the owner answered §2b: write her answers into the project
README beside the decision-interview answers, and, where she chose or brought a palette, fill `BRAND.md`'s
colour tables with it (`website-brand-kit` §5a says what goes where). These documents
are all that step 2b writes before the tests are green.
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.
**If the owner has a palette or a logo from §2b**, run `website-brand-kit` §6 (the
intake) now, on the green baseline and before the first page: it checks the palette
and the logo, writes them into the site, runs these same tests again and puts every
file back if they go red. Commit once it is green.
## 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.
With Plausible: the `Email Tap` goal exists in Plausible (§1 Q5).
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).
- [ ] **Dependabot security fixes on** for the site's GitHub repo: run `SETUP.md`'s
Dependabot lines from the site folder; the output must name this repo and show
`alerts: on` and `security updates: on`. Not skippable: the site's lockfile is its
own, so no suite update ever patches it.
- [ ] **`<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".
- [ ] **Credit settled** (§4b), after the site is live: asked once, or found already
in the footer, and the outcome recorded in the project `README.md`. "Declined" is
a complete, valid outcome.
## 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`.
## 4b. A credit for Croftweaver — ask once, after the site is live
Croftweaver is free and its license asks for no credit. The people who make it ask for
one anyway: a small "Built with Croftweaver" link in the footer, and a star on GitHub.
It is a request, never a condition, and this is the only place the pipeline raises it.
This step belongs to the launch checklist (§4). Do not raise it in a later session
that is doing other work on the site, with one exception, an unfinished yes: the
README says "credit: badge" or "credit: text link" and the footer of the live site has
no link to `croftweaver.com`. Then the work was cut short somewhere before it was
published, or the credit was taken out since. Say so once. On a yes, do whatever is
left of the steps below: paste, test, publish. On any other answer, or none, change
the line to "credit: declined". That is not a second ask.
Ask only when all three hold; otherwise do not ask:
- **The site is live**: the deploy item of the §4 checklist is done and `npm test` is
green. Never during the build and never while something is failing.
- **The project `README.md` has no credit line** among the decision answers. A line
there means the question was put already, whatever the line says.
- **The footer in the site's source does not link to `croftweaver.com` yet** (the
starter keeps it in `src/layouts/Base.astro`). If it does, record "credit:
already in the footer" in the README and stop.
Then ask once, in one message — a structured user-input tool if the platform offers
one (Claude Code's **`AskUserQuestion`**), otherwise in chat:
> Your site is live. I hope you enjoyed building it.
>
> The people who make Croftweaver have one request, and it is only a request: would
> you add a small "Built with Croftweaver" credit to your footer? It links to
> croftweaver.com and helps others find the toolkit. I can add it now, as a badge or
> as a plain text link. If the site belongs to a customer of yours, it is their call.
>
> And if you liked Croftweaver, a star on GitHub helps too:
> https://github.com/karero/croftweaver
Options: **Add the badge** / **Add a text link** / **No thanks**.
- **Record the answer first**, before any other step, with the decision answers in
the project README: "credit: badge", "credit: text link" or "credit: declined". No
later session asks again then, even if this one is cut short.
- **Add the badge** or **Add a text link** → take the code from
`https://croftweaver.com/partner-assets`, which is its only source; do not write it
from memory. Use the page's HTML code, never its Markdown code, which is for a
README. The HTML is one `<a>` link around one `<img>` (alone or inside a
`<picture>`), or the words "Built with" and one `<a>` text link. If the page offers
anything else, such as a script, a style or an attribute that runs code, do not
paste it: tell the user. For the badge, save each
file the chosen code's note names, from
`https://croftweaver.com/brand/badge/<file name>`, into `public/brand/badge/`: the
starter's security rules block images from other sites. Put the code inside
`<footer class="site-footer">` in `src/layouts/Base.astro`, after the legal links.
Look at the footer in light and in dark, and with the site's own theme switch if it
has one, and pick the badge that reads well in each. Run `npm test`; if a test
objects to the pasted code, tell the user and do not change the test to make the
credit fit. Then publish per the publish model (§1 Q6) with the usual
preview-or-live announcement.
- **No thanks** → say "No problem" and nothing more.
- **No answer** → say nothing more about it, and record it as declined.
Never add the credit unasked, never ask a second time, and never give the star for
the user: it is their own click on their own account. Do not word the request as
something owed ("in return", "to support us").
## 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.