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
9 changes: 5 additions & 4 deletions .claude/commands/sync-template.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ allowed-tools: Read, Edit, Bash, Agent

Update this project's agent files with the latest CORE sections from the template repository.

1. If `scripts/sync-from-template.sh` exists, run it. Otherwise tell Tim that the sync script arrives in Stage 6 of the build, and stop.
2. The sync replaces only the content between the `<!-- BEGIN CORE -->` and `<!-- END CORE -->` markers in each agent file. It never touches the PROJECT OVERLAY section, so project-specific customisations are safe.
3. Show Tim a clear summary of what changed in each agent file.
4. Opening a pull request for the sync is a GitHub action: pre-approve it in the brief, or get Tim's approval at the time.
1. Always do the sync on one stable branch: `chore/template-sync`. Never name the branch after the template version, and never invent a new name per run. If the branch already exists, check it out and reuse it; if not, create it. One stable branch means each repository holds at most one sync branch at a time, and a new sync updates the existing pull request instead of leaving a trail of version-named branches behind. This is a deliberate anti-clutter rule; see `docs/release-process.md` under "Post-merge branch cleanup" for why.
2. If `scripts/sync-from-template.sh` exists, run it. Otherwise tell Tim that the sync script arrives in Stage 6 of the build, and stop.
3. The sync replaces only the content between the `<!-- BEGIN CORE -->` and `<!-- END CORE -->` markers in each agent file. It never touches the PROJECT OVERLAY section, so project-specific customisations are safe.
4. Show Tim a clear summary of what changed in each agent file.
5. Open or update the pull request from `chore/template-sync`. If an open sync pull request already exists, push to its branch so it updates in place rather than opening a second one. Opening a pull request is a GitHub action: pre-approve it in the brief, or get Tim's approval at the time.
2 changes: 1 addition & 1 deletion .claude/template-version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.9.0
1.9.3
6 changes: 3 additions & 3 deletions .github/accessibility-tools/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions .github/lint-tools/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

51 changes: 40 additions & 11 deletions .github/workflows/accessibility.yml
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,17 @@
# check. This is a genuine "not applicable," not a deferred check. First
# introduced as a project-local fix in Lire RSS Hermes
# (docs/decisions/001-python-tool-ci-archetype.md in that project).
#
# Archetype-aware skip for wordpress-theme. A project whose
# .github/ci-archetype declares wordpress-theme is a classic WordPress
# theme: its pages are PHP templates rendered by a live WordPress
# instance, not static HTML this job can serve directly. There is no
# lightweight way to stand up a full WordPress + database install in
# this job, so every scan step is skipped, with its own log-only
# explanation. Like python-tool, this is a genuine "not applicable" for
# now, not a deferred check; a future wp-env-based job would be the
# real fix. First introduced as a project-local fix in Tim Dixon
# WordPress Theme and LLBS-Website.

name: Accessibility
on:
Expand Down Expand Up @@ -124,9 +135,14 @@ jobs:
else
echo "is_python_tool=false" >> "$GITHUB_OUTPUT"
fi
if [ "$value" = "wordpress-theme" ]; then
echo "is_wordpress_theme=true" >> "$GITHUB_OUTPUT"
else
echo "is_wordpress_theme=false" >> "$GITHUB_OUTPUT"
fi

- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
if: steps.archetype.outputs.is_browser_extension != 'true' && steps.archetype.outputs.is_python_tool != 'true'
if: steps.archetype.outputs.is_browser_extension != 'true' && steps.archetype.outputs.is_python_tool != 'true' && steps.archetype.outputs.is_wordpress_theme != 'true'
with:
node-version: lts/*
cache: npm
Expand All @@ -135,17 +151,17 @@ jobs:
- name: Install accessibility tools
# Tools are managed in .github/accessibility-tools/package.json so
# Dependabot can track and propose version bumps automatically.
if: steps.archetype.outputs.is_browser_extension != 'true' && steps.archetype.outputs.is_python_tool != 'true'
if: steps.archetype.outputs.is_browser_extension != 'true' && steps.archetype.outputs.is_python_tool != 'true' && steps.archetype.outputs.is_wordpress_theme != 'true'
run: npm ci
working-directory: .github/accessibility-tools

- name: Add accessibility tools to PATH
if: steps.archetype.outputs.is_browser_extension != 'true' && steps.archetype.outputs.is_python_tool != 'true'
if: steps.archetype.outputs.is_browser_extension != 'true' && steps.archetype.outputs.is_python_tool != 'true' && steps.archetype.outputs.is_wordpress_theme != 'true'
run: echo "$GITHUB_WORKSPACE/.github/accessibility-tools/node_modules/.bin" >> "$GITHUB_PATH"

- name: Set up Chrome and ChromeDriver
id: chrome
if: steps.archetype.outputs.is_browser_extension != 'true' && steps.archetype.outputs.is_python_tool != 'true'
if: steps.archetype.outputs.is_browser_extension != 'true' && steps.archetype.outputs.is_python_tool != 'true' && steps.archetype.outputs.is_wordpress_theme != 'true'
uses: browser-actions/setup-chrome@2e1d749697dd1612b833dba4a722266286fbefcd # v2.1.2
with:
install-chromedriver: true
Expand All @@ -155,29 +171,29 @@ jobs:
# the repository root). A build is required before serving because
# a bundler's index.html loads ES module imports that only resolve
# once built.
if: steps.archetype.outputs.is_browser_extension != 'true' && steps.archetype.outputs.is_python_tool != 'true' && hashFiles('package.json') != ''
if: steps.archetype.outputs.is_browser_extension != 'true' && steps.archetype.outputs.is_python_tool != 'true' && steps.archetype.outputs.is_wordpress_theme != 'true' && hashFiles('package.json') != ''
run: npm ci

- name: Build the project (npm)
# --if-present no-ops when the project has no "build" script.
if: steps.archetype.outputs.is_browser_extension != 'true' && steps.archetype.outputs.is_python_tool != 'true' && hashFiles('package.json') != ''
if: steps.archetype.outputs.is_browser_extension != 'true' && steps.archetype.outputs.is_python_tool != 'true' && steps.archetype.outputs.is_wordpress_theme != 'true' && hashFiles('package.json') != ''
run: npm run build --if-present

- name: Serve the built site in the background (static-app / browser-extension, build present)
if: steps.archetype.outputs.is_browser_extension != 'true' && steps.archetype.outputs.is_python_tool != 'true' && hashFiles('package.json') != '' && hashFiles('dist/**') != ''
if: steps.archetype.outputs.is_browser_extension != 'true' && steps.archetype.outputs.is_python_tool != 'true' && steps.archetype.outputs.is_wordpress_theme != 'true' && hashFiles('package.json') != '' && hashFiles('dist/**') != ''
run: python3 -m http.server 8080 --directory dist &

- name: Serve the repository root in the background (static-content, or a bundler-free package.json project with no dist/ output)
if: steps.archetype.outputs.is_browser_extension != 'true' && steps.archetype.outputs.is_python_tool != 'true' && hashFiles('dist/**') == ''
if: steps.archetype.outputs.is_browser_extension != 'true' && steps.archetype.outputs.is_python_tool != 'true' && steps.archetype.outputs.is_wordpress_theme != 'true' && hashFiles('dist/**') == ''
run: python3 -m http.server 8080 &

- name: Wait for the server to be ready
if: steps.archetype.outputs.is_browser_extension != 'true' && steps.archetype.outputs.is_python_tool != 'true'
if: steps.archetype.outputs.is_browser_extension != 'true' && steps.archetype.outputs.is_python_tool != 'true' && steps.archetype.outputs.is_wordpress_theme != 'true'
run: wait-on http://localhost:8080 --timeout 15000

- name: Pa11y at WCAG 2.2 AAA
# Adapt the URL to match the project's entry point.
if: steps.archetype.outputs.is_browser_extension != 'true' && steps.archetype.outputs.is_python_tool != 'true'
if: steps.archetype.outputs.is_browser_extension != 'true' && steps.archetype.outputs.is_python_tool != 'true' && steps.archetype.outputs.is_wordpress_theme != 'true'
env:
PUPPETEER_EXECUTABLE_PATH: ${{ steps.chrome.outputs.chrome-path }}
run: pa11y --standard WCAG2AAA http://localhost:8080/index.html
Expand All @@ -199,7 +215,7 @@ jobs:
# meet 7:1 (identical-commit rerun on SWOT Builder went from 9
# violations to 0). --load-delay is a settle delay only; it does not
# change which rules run or weaken --exit.
if: steps.archetype.outputs.is_browser_extension != 'true' && steps.archetype.outputs.is_python_tool != 'true'
if: steps.archetype.outputs.is_browser_extension != 'true' && steps.archetype.outputs.is_python_tool != 'true' && steps.archetype.outputs.is_wordpress_theme != 'true'
env:
CHROMEDRIVER_PATH: ${{ steps.chrome.outputs.chromedriver-path }}
CHROME_PATH: ${{ steps.chrome.outputs.chrome-path }}
Expand Down Expand Up @@ -229,3 +245,16 @@ jobs:
echo "browser-extension, there is no alternate accessibility gate"
echo "to point to here; this is a genuine \"not applicable\", not"
echo "a deferred check."

- name: Note that this project has no lightweight rendered surface to scan (wordpress-theme)
if: steps.archetype.outputs.is_wordpress_theme == 'true'
run: |
echo "This project's .github/ci-archetype declares wordpress-theme."
echo "Its pages are PHP templates rendered by a live WordPress"
echo "instance, not static HTML this job can serve directly, so"
echo "this job is a deliberate no-op for this archetype, not a"
echo "weakened check. There is no alternate automated accessibility"
echo "gate here yet; this is a genuine \"not applicable\" for now,"
echo "not a deferred check. AAA conformance for this project is"
echo "verified by Carol's manual test pass against the live or"
echo "staged site."
47 changes: 31 additions & 16 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,11 @@
# code-type project that has neither manifest — a hand-written static site
# with no package.json and no build step. Its contents (trimmed of
# whitespace) must be one of: static-content, static-app, browser-extension,
# php, python-tool. Only "static-content" and "python-tool" excuse a project
# from the no-manifest failure below; the other three values exist so the
# file is a complete, self-documenting record of the project's archetype
# even where a manifest already tells CI what to do. See
# jacob-ci-archetypes.md section 2b.
# php, python-tool, wordpress-theme. Only "static-content", "python-tool",
# and "wordpress-theme" excuse a project from the no-manifest failure below;
# the other values exist so the file is a complete, self-documenting record
# of the project's archetype even where a manifest already tells CI what to
# do. See jacob-ci-archetypes.md section 2b.
# A project with neither a manifest nor an excusing declaration fails the
# job, because a code-type project is expected to have one or the other.
#
Expand All @@ -32,6 +32,15 @@
# project-local fix in Lire RSS Hermes
# (docs/decisions/001-python-tool-ci-archetype.md in that project) and
# upstreamed here.
#
# wordpress-theme: a classic WordPress theme with PHP templates, no
# composer.json (no Composer dependencies), and often no package.json
# either (or one used purely for lint tooling, with no build or test
# script). There is no lightweight way to run a real WordPress instance in
# this job, so this archetype has no build-equivalent check here beyond the
# manifest excuse; accessibility.yml separately gives it an honest no-op
# for the same reason (no live render available in CI). First introduced as
# a project-local fix in Tim Dixon WordPress Theme and LLBS-Website.

name: CI
on:
Expand Down Expand Up @@ -126,12 +135,12 @@ jobs:
# instance via wp-env and belong in a dedicated job once a WordPress
# project reaches that point. See docs/stacks/wordpress.md.

- name: Check for a static-content or python-tool archetype declaration
- name: Check for a static-content, python-tool, or wordpress-theme archetype declaration
# Reads .github/ci-archetype, trimmed of surrounding whitespace and
# newlines, and records whether it declares "static-content" or
# "python-tool". Runs unconditionally (cheap) so the fail-check
# below can read the result regardless of which manifests are
# present.
# newlines, and records whether it declares "static-content",
# "python-tool", or "wordpress-theme". Runs unconditionally (cheap)
# so the fail-check below can read the result regardless of which
# manifests are present.
id: archetype
run: |
if [ -f .github/ci-archetype ]; then
Expand All @@ -149,19 +158,25 @@ jobs:
else
echo "is_python_tool=false" >> "$GITHUB_OUTPUT"
fi
if [ "$value" = "wordpress-theme" ]; then
echo "is_wordpress_theme=true" >> "$GITHUB_OUTPUT"
else
echo "is_wordpress_theme=false" >> "$GITHUB_OUTPUT"
fi

- name: Fail if no manifest or excusing declaration was found
if: |
hashFiles('package.json') == '' &&
hashFiles('composer.json') == '' &&
steps.archetype.outputs.is_static_content != 'true' &&
steps.archetype.outputs.is_python_tool != 'true'
steps.archetype.outputs.is_python_tool != 'true' &&
steps.archetype.outputs.is_wordpress_theme != 'true'
run: |
echo "No package.json or composer.json found at the repository root,"
echo "and .github/ci-archetype does not declare \"static-content\""
echo "or \"python-tool\". This is a code-type project, so one of"
echo "these is expected. See the comments at the top of this"
echo "workflow for what each manifest or declaration means."
echo "and .github/ci-archetype does not declare \"static-content\","
echo "\"python-tool\", or \"wordpress-theme\". This is a code-type"
echo "project, so one of these is expected. See the comments at the"
echo "top of this workflow for what each manifest or declaration means."
exit 1

# --- Python command-line tool or automation script ---
Expand All @@ -180,5 +195,5 @@ jobs:
if: steps.archetype.outputs.is_python_tool == 'true'
run: |
python3 -m compileall -q \
--exclude '(^\./\.git/|^\./node_modules/|^\./\.claude/)' \
-x '(^\./\.git/|^\./node_modules/|^\./\.claude/)' \
.
36 changes: 36 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -63,24 +63,56 @@ jobs:
# DELETE this job if your project is UNBUNDLED.
build:
runs-on: ubuntu-latest
outputs:
is_browser_extension: ${{ steps.archetype.outputs.is_browser_extension }}
steps:
- name: Check out the code
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0

- name: Check the CI archetype
# Reads .github/ci-archetype, trimmed of surrounding whitespace and
# newlines, mirroring the equivalent step in ci.yml and
# accessibility.yml. A browser-extension project has no public
# website at all — nothing for GitHub Pages to serve — so this
# entire workflow is a deliberate no-op for that archetype, not a
# weakened deploy. See jacob-ci-archetypes.md section 2c.
id: archetype
run: |
if [ -f .github/ci-archetype ]; then
value=$(tr -d '[:space:]' < .github/ci-archetype)
else
value=""
fi
if [ "$value" = "browser-extension" ]; then
echo "is_browser_extension=true" >> "$GITHUB_OUTPUT"
else
echo "is_browser_extension=false" >> "$GITHUB_OUTPUT"
fi

- name: Note that browser-extension projects have nothing to deploy
if: steps.archetype.outputs.is_browser_extension == 'true'
run: |
echo "This project's .github/ci-archetype declares browser-extension."
echo "A browser extension has no public website for GitHub Pages to"
echo "serve, so this workflow is a deliberate no-op for this archetype."

- name: Set up Node.js
if: steps.archetype.outputs.is_browser_extension != 'true'
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: lts/*
cache: npm

- name: Install dependencies
if: steps.archetype.outputs.is_browser_extension != 'true'
run: npm ci

- name: Build
# --if-present no-ops when the project has no "build" script (a
# bundler-free static-app project with a package.json for tooling
# only, e.g. Braille Reference). Without --if-present, npm exits
# non-zero with "Missing script: build" and the deploy fails.
if: steps.archetype.outputs.is_browser_extension != 'true'
run: npm run build --if-present

- name: Assemble the deploy artifact
Expand Down Expand Up @@ -115,6 +147,7 @@ jobs:
# unanchored 'data/***' also matches node_modules/css-tree/data/*,
# silently publishing node_modules internals to the public site.
id: artifact
if: steps.archetype.outputs.is_browser_extension != 'true'
run: |
if [ -d dist ]; then
echo "path=dist" >> "$GITHUB_OUTPUT"
Expand All @@ -136,15 +169,18 @@ jobs:
fi

- name: Configure Pages
if: steps.archetype.outputs.is_browser_extension != 'true'
uses: actions/configure-pages@45bfe0192ca1faeb007ade9deae92b16b8254a0d # v6.0.0

- name: Upload Pages artifact
if: steps.archetype.outputs.is_browser_extension != 'true'
uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0
with:
path: ${{ steps.artifact.outputs.path }}

deploy:
needs: build
if: needs.build.outputs.is_browser_extension != 'true'
runs-on: ubuntu-latest
environment:
name: github-pages
Expand Down
Loading