Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ Because inline copies can drift, run the `check-content-alignment` skill after e

## Building, previewing, and verifying

The tooling for building, previewing, and verifying the site — dev server, clean build, the page-count invariant, and the lychee link check — lives in the [`build-and-verify-docs`](skills/build-and-verify-docs/SKILL.md) skill. Run that verification sequence before every commit, and don't commit if any step fails.
The tooling for building, previewing, and verifying the site lives in the [`build-and-verify-docs`](skills/build-and-verify-docs/SKILL.md) skill. Run that verification sequence before every commit, and don't commit if any step fails. Derive expected pages from the current content and configuration rather than fixed totals.

Before opening or updating a PR, also make the **PR-time consistency pass** documented in that skill — a structural-drift sweep (renamed paths, stale skill/instruction references, CI claims, repository-structure trees, and copied prose alignment) that the build and link check can't catch.

Expand Down
2 changes: 1 addition & 1 deletion .github/instructions/astro.instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,4 @@ This is a docs wrapper. Don't add interactive framework islands (Svelte, React,

## Building and verifying

After changing `astro.config.mjs` or anything under `website/src/`, build and verify the site with the [`build-and-verify-docs`](../skills/build-and-verify-docs/SKILL.md) skill. Its page-count invariant is the tripwire for unexpected routed pages: Starlight emits each of the 36 workshop routes for the root language and five configured locales, then adds the legacy redirect, for 217 built `index.html` pages excluding the 404 page. If the count changes without a corresponding route or locale change, check the locale layout under `docs/` and the underscore-directory exclude in `src/content.config.ts`.
After changing `astro.config.mjs` or anything under `website/src/`, use the [`build-and-verify-docs`](../skills/build-and-verify-docs/SKILL.md) skill. Check affected routes and translations against the current source, locale configuration, and content-loader rules. Confirm support assets remain excluded from routing; do not maintain fixed page totals in authoring guidance.
10 changes: 5 additions & 5 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,12 @@

## Verification

<!-- Confirm the build + link checks pass before requesting review. -->
<!-- Follow .github/skills/build-and-verify-docs/SKILL.md. Mark non-applicable checks with a brief explanation. -->

- [ ] `cd website && rm -rf dist && npm run build` succeeds (target: 36 routes × 6 locales + 1 redirect = 217 built pages excluding 404; build reports 218 HTML files including 404, or note any intentional change)
- [ ] Lychee link check passes:
`mkdir -p /tmp/lychee-root && ln -sfn $PWD/website/dist /tmp/lychee-root/copilot-workshops && lychee --offline --no-progress --root-dir /tmp/lychee-root 'website/dist/**/*.html'`
- [ ] External GitHub URLs that I changed have been clicked manually (lychee runs offline)
- [ ] Type checks and a clean site build pass.
- [ ] Affected pages and translations match their source and intended routes.
- [ ] Offline internal link checks pass.
- [ ] Changed external links have been checked separately.

## Screenshots

Expand Down
4 changes: 2 additions & 2 deletions .github/skills/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ These encode conventions unique to this Astro + Starlight workshop content repo.

| Skill | What it does | Use it when |
|---|---|---|
| [`build-and-verify-docs`](./build-and-verify-docs/SKILL.md) | Canonical build/preview/verify process: dev server, clean build, page-count invariant, lychee link check, and the **PR-time consistency pass** for structural drift. | Building, previewing, or verifying any change under `docs/`; before every commit/PR. |
| [`check-content-alignment`](./check-content-alignment/SKILL.md) | Scans a diff (staged/unstaged or a branch range) for content changes, then finds other lessons that duplicate or parallel the changed prose — formerly-shared passages now copied across pages, and the same concept taught across the VS Code / CLI / App / Cloud harnesses — so duplicated copies don't drift out of sync. Reports candidate files and line ranges; does not edit content. | After editing lesson content under `docs/`, before committing/opening a PR, to find other pages that should change too. |
| [`build-and-verify-docs`](./build-and-verify-docs/SKILL.md) | Small Bash and PowerShell wrappers for existing type checks, build, and offline links; AI guidance for source-page, semantic, visual, and structural review. | Building, previewing, or verifying the site; before every commit/PR. |
| [`check-content-alignment`](./check-content-alignment/SKILL.md) | Uses the diff and current content tree to find inconsistent repeated passages, parallel concepts, translations, and cross-references. Reports candidates without editing content. | After lesson edits or when reviewing content drift. |
| [`validate-site-playwright`](./validate-site-playwright/SKILL.md) | Optional deeper **browser** QA: drives the Playwright MCP server against a local preview to confirm pages render, catch console/hydration errors, find broken images, and confirm Starlight components mounted. | A deeper render/visual pass before a PR that changes how pages render. Complements (doesn't replace) `build-and-verify-docs`. |

## General-purpose skills
Expand Down
113 changes: 27 additions & 86 deletions .github/skills/build-and-verify-docs/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,115 +1,56 @@
---
name: build-and-verify-docs
description: Build, preview, and verify the Copilot Workshops Astro + Starlight workshop site before committing or opening a PR. Use whenever an author or agent is about to build the site, run a local preview/dev server, check links with lychee, confirm the page-count invariant, run the pre-commit verification sequence for any change under `docs/` (content) or `website/` (tooling), or make a PR-time consistency pass to catch structural drift (renamed paths, stale skill/instruction references, inaccurate CI claims, out-of-date structure trees).
description: Build, preview, and verify the workshop site using existing checks. Use before committing or updating a PR. Inspect affected content and translations separately rather than relying on fixed page counts.
---

# Build and verify the docs site

The workshop content is plain Markdown in the repo-root `docs/` directory; the Astro + Starlight site that publishes it lives in `website/` (sourcing content via the loader's `base: '../docs'`). This skill is the single source of truth for **how to build, preview, and verify** that site. The instruction files (`.github/instructions/*`, `.github/copilot-instructions.md`) describe *what content should look like*; this skill describes *how to run the tooling*.
Lesson source lives in `docs/`; the Astro + Starlight publisher lives in `website/`. This skill owns the verification procedure. Other authoring guidance should link here instead of repeating commands.

Run every command from the **repo root** unless a step says otherwise.
## Run existing checks

## When to use

Trigger this skill whenever you:

- are about to build the site (`npm run build`) or start the dev server,
- need to preview content locally,
- are running the pre-commit / pre-PR verification pass on any change under `docs/` (content) or `website/` (tooling),
- want to confirm the page-count invariant or check links,
- are about to open or update a PR that touches `docs/` or `website/`.

For an optional **deeper, browser-based** pass that confirms pages actually render (console errors, broken images, mounted components), use the [`validate-site-playwright`](../validate-site-playwright/SKILL.md) skill after the static checks here.

## Local preview

The Astro dev server is the primary preview surface (hot reload):
Use the wrapper for your shell, from the repository root or by absolute script path from any directory:

```bash
cd website && npm install && npm run dev
bash .github/skills/build-and-verify-docs/scripts/verify.sh
```

Open <http://localhost:4321/copilot-workshops/>. Lesson content lives in the repo-root `docs/` directory; the loader sources it via `base: '../docs'`, so no symlinks are required for preview.

## Verification sequence (run before every commit)

Run all three. Don't commit if any fails.

### 1. Build (clean)

```bash
cd website && rm -rf dist && npm run build
```powershell
& ./.github/skills/build-and-verify-docs/scripts/verify.ps1
```

### 2. Page-count invariant

The workshop has 36 distinct route slugs. Starlight emits each route for the English root locale and the five configured localized routes, using English fallback content when a translation is unavailable. The built site therefore contains $36 \times 6 = 216$ workshop routes plus the one legacy redirect (`/shared/0-prereqs/`, authored as a full-HTML redirect page at `website/src/pages/shared/0-prereqs.astro`). The expected count is 217 `index.html` files when excluding the 404 page. Astro reports 218 HTML files because it includes the 404 page.

```bash
# distinct route slugs in the English root locale (docs/README.md + docs/<harness>/*.md)
find docs -maxdepth 2 -name '*.md' \
! -path 'docs/es-es/*' \
! -path 'docs/ja-jp/*' \
! -path 'docs/ko-kr/*' \
! -path 'docs/pt-br/*' \
! -path 'docs/zh-cn/*' | wc -l
# built pages (excludes the 404)
find website/dist -name index.html | grep -v 404 | wc -l
```
Use an installed Node.js/npm version supported by `website/`, existing site dependencies, and Lychee on `PATH`. Ask before installing missing software. Run PowerShell under your normal execution policy; do not bypass it. The Bash wrapper has been run on macOS; the PowerShell wrapper has not been executed or parser-validated because PowerShell was unavailable.

`built pages` should equal `(distinct route slugs × configured locales) + 1`. If the build emits **more** pages than that without a matching route or locale change, confirm localized content is directly under `docs/<locale>/` rather than an extra parent directory, then check the underscore-directory exclude in `website/src/content.config.ts`; it is still needed so support directories such as `_images/` are not routed as pages.
Both wrappers run `npm run check:all`, remove only `website/dist` (refusing a linked output directory), run `npm run build`, then run `lychee --offline --no-progress` against the built HTML. Package scripts remain the source of truth. Output is not suppressed, failures stop verification, and native command exit codes are returned. Each run creates and cleans its own temporary link root. Like the existing Pages workflow, the wrappers map `/copilot-workshops/` to the build; update that mapping alongside the workflow if the configured site base changes.

### 2b. Translations actually render (not silent English fallback)
Stop on failure and resolve it before committing. The wrappers do not install dependencies, start servers, compare source text with HTML, or validate translation quality. Offline Lychee checks internal links and images, not external destinations.

The build and the page-count above are **blind to which content actually renders** — a mis-nested or wrongly-identified locale tree still emits 217 pages served from English fallback. Assert that a known translated page carries translated text and the right `lang` attribute:
## Inspect affected content

```bash
grep -o '<title>[^<]*</title>' website/dist/es-es/app/2-add-star-rating/index.html # Spanish title
grep -o 'lang="[^"]*"' website/dist/es-es/app/2-add-star-rating/index.html | head -1 # lang="es-ES"
```
Use the diff, source paths and `slug` frontmatter, `website/src/content.config.ts`, and `website/astro.config.mjs` to select and inspect affected output. Do not use a fixed page total as proof of correctness.

The Spanish title should read `Lección 2 - Ejecutar tu primera sesión de agente`, not the English string. Spot-check a second locale (e.g. `ja-jp` -> `lang="ja-JP"`).
- Confirm changed lessons appear at their intended routes with their titles, headings, prompts, and admonitions rendered correctly.
- Compare translated pages with their locale source and language; English fallback is expected only when a translation is absent.
- Check references and navigation after renames or removals, and confirm support assets are not published as lessons.
- Review semantic accuracy, teaching flow, translation quality, intentional harness differences, and whether navigation or redirects lead to the appropriate next task.

### 3. Link check (lychee, offline)
These inspections are not automated by the wrappers. Use the [browser validation skill][browser-validation] for layout, styling, client-side behavior, console errors, and visual image loading. Existing plain-div admonition styling is not fixed or certified by a successful build. Open changed external links separately and confirm their intended destinations. Report any blocked checks honestly.

The site builds with `base=/copilot-workshops/`, so internal hrefs are absolute (`/copilot-workshops/foo/`). Symlink that prefix to `website/dist` so lychee can follow internal links:
## Local preview

```bash
mkdir -p /tmp/lychee-root && ln -sfn "$PWD/website/dist" /tmp/lychee-root/copilot-workshops \
&& lychee --offline --no-progress --root-dir /tmp/lychee-root 'website/dist/**/*.html'
(cd website && npm run dev)
```

Lychee runs offline and won't catch broken **external** GitHub URLs. When you change absolute `https://github.com/...` links, click through them manually.

## What CI enforces vs. what is local-only

`.github/workflows/pages.yml` runs on PRs and on push to `main`. It runs **only**:
Open the URL printed by the server, including the configured base path. Stop only the server you started when finished.

1. `npm ci`
2. `npm run build` (Astro build) — must succeed
3. lychee offline link check against `website/dist/` — must pass
## Consistency before updating a PR

After a push to `main`, `pages.yml` deploys `website/dist` to GitHub Pages. Browser validation and content-alignment analysis are separate optional/safety-net workflows, not part of the Pages build job.
- Search for references to paths, names, or conventions changed by the diff, including repository guidance and navigation.
- Use the [content alignment skill][content-alignment] for duplicated lesson passages and translations that may need matching changes.
- Keep CI descriptions aligned with `website/package.json` and `.github/workflows/pages.yml`.

## PR-time consistency pass
The Pages workflow remains unchanged: it runs type checks, the build, and offline link validation, with deployment restricted to pushes to `main`. Source-page inspection, browser validation, and content-alignment review remain separate.

The build and link check above catch *mechanical* breakage. They do **not** catch *structural drift* — prose and reference material that silently falls out of sync when files move or conventions change. Before opening or updating a PR, make a consistency pass over everything your change touched:

- **Renamed or moved a file or folder?** Grep the whole repo for the old path and update every hit — `.md`, instruction files, skills, and the repository-structure trees in `README.md`, `docs/README.md`, `website/README.md`, `AUTHORING.md`, and `.github/copilot-instructions.md`. Example: when `images/` became `_images/`, every `../images/...` reference and every structure tree had to change.
- **Added or removed a skill, agent, instruction file, or workflow?** Grep for references to the old name and remove them. Add a new `.github/instructions/*.instructions.md` file to the **Deeper conventions** list in `AUTHORING.md`, and to the structure block in `.github/copilot-instructions.md` if it's structural.
- **Changed duplicated lesson prose?** Run the `check-content-alignment` skill to identify other inline copies that need the same update. The `.github/workflows/content-alignment.md` agentic workflow runs the same analysis on PRs as a safety net.
- **Described what CI does anywhere?** Confirm it matches `.github/workflows/pages.yml`, which runs the build and the lychee link check.
- **Changed the build or verify steps?** This skill is the single source of truth. `README.md`, `AUTHORING.md`, and `CONTRIBUTING.md` should *point here*, not re-document the commands. Keep any summary in those files consistent with this skill.
- **Repository-structure trees** in `README.md`, `docs/README.md`, `website/README.md`, `AUTHORING.md`, and `.github/copilot-instructions.md` should all reflect the real tree. If you add or rename a top-level content directory, update all of them.
- **Page-count invariant** (section 2 above) should still hold after the build.

When in doubt, `grep -rn "<old-name>" --include='*.md' .` (excluding `node_modules` and `website/dist`) is the fastest way to surface stale references.

## Quick reference

```bash
# from repo root
cd website && rm -rf dist && npm run build && cd ..
mkdir -p /tmp/lychee-root && ln -sfn "$PWD/website/dist" /tmp/lychee-root/copilot-workshops \
&& lychee --offline --no-progress --root-dir /tmp/lychee-root 'website/dist/**/*.html'
```
[browser-validation]: ../validate-site-playwright/SKILL.md
[content-alignment]: ../check-content-alignment/SKILL.md
53 changes: 53 additions & 0 deletions .github/skills/build-and-verify-docs/scripts/verify.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
$ErrorActionPreference = 'Stop'
$PSNativeCommandUseErrorActionPreference = $false
$exitCode = 1
$linkRoot = $null
$link = $null
$locationPushed = $false

try {
$repo = (Resolve-Path (Join-Path $PSScriptRoot '../../../..')).Path
$website = Join-Path $repo 'website'
Push-Location -LiteralPath $website
$locationPushed = $true
$npm = if ($env:OS -eq 'Windows_NT') { 'npm.cmd' } else { 'npm' }
Get-Command $npm, lychee -ErrorAction Stop | Out-Null
if (!(Test-Path -LiteralPath 'astro.config.mjs' -PathType Leaf) -or
!(Test-Path -LiteralPath 'package.json' -PathType Leaf)) {
throw 'Expected the workshop website configuration and package.json.'
}

& $npm run check:all
if ($LASTEXITCODE -ne 0) { $exitCode = $LASTEXITCODE; throw 'Type checks failed.' }
$dist = Join-Path $website 'dist'
if (Test-Path -LiteralPath $dist) {
$existing = Get-Item -LiteralPath $dist -Force
if (!$existing.PSIsContainer -or ($existing.Attributes -band [IO.FileAttributes]::ReparsePoint)) {
throw 'Refusing to remove dist: expected a directory, not a link or file.'
}
Remove-Item -LiteralPath $dist -Recurse -Force
}
& $npm run build
if ($LASTEXITCODE -ne 0) { $exitCode = $LASTEXITCODE; throw 'Site build failed.' }
$built = Get-Item -LiteralPath $dist -Force
if (!$built.PSIsContainer -or ($built.Attributes -band [IO.FileAttributes]::ReparsePoint)) {
throw 'Build did not produce a regular dist directory.'
}

$linkRoot = Join-Path ([IO.Path]::GetTempPath()) ('workshop-links-' + [guid]::NewGuid())
New-Item -ItemType Directory -Path $linkRoot | Out-Null
# Match the current base in website/astro.config.mjs, as the Pages workflow does.
$link = Join-Path $linkRoot 'copilot-workshops'
$linkType = if ($env:OS -eq 'Windows_NT') { 'Junction' } else { 'SymbolicLink' }
New-Item -ItemType $linkType -Path $link -Target $dist | Out-Null
& lychee --offline --no-progress --root-dir $linkRoot (Join-Path $dist '**/*.html')
$exitCode = $LASTEXITCODE
} catch {
Write-Error $_ -ErrorAction Continue
} finally {
# Delete the link itself, never recurse into the built site through it.
if ($link -and (Test-Path -LiteralPath $link)) { [IO.Directory]::Delete($link) }
if ($linkRoot -and (Test-Path -LiteralPath $linkRoot)) { [IO.Directory]::Delete($linkRoot) }
if ($locationPushed) { Pop-Location }
}
exit $exitCode
Loading
Loading