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
1 change: 1 addition & 0 deletions .Rbuildignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ _freeze/
^\.github$
^README\.Rmd$
^data-raw$
^cds$
^\.quarto$
^AGENTS\.md$
^CLAUDE\.md$
Expand Down
14 changes: 13 additions & 1 deletion .claude/commands/lint.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,22 @@
---
description: Lint all R source files and report issues
allowed-tools:
- Bash(rm -f cds)
- Bash(Rscript -e 'lintr::lint*')
---

Run `Rscript -e 'lintr::lint_dir()'` to check all R source files against `.lintr.R`.
First run `rm -f cds` to remove the repo's self-referential `cds -> .` symlink
(see CLAUDE.md) from the working tree. `lintr::lint_dir()`'s own directory
walk follows symlinks and has no config-level way to skip it (any
`.lintr.R` exclusion naming `cds` collapses onto the real files it aliases
and silently excludes them too --- see `.lintr.R`'s comment), so with the
symlink present every real lint is duplicated dozens of times over. Removing
it first is safe: it only deletes the symlink, never the directory it points
into.

Then run `Rscript -e 'lintr::lint_dir()'` to check all R source files against `.lintr.R`.
Afterwards, whether or not lint succeeds, restore the symlink with
`git checkout -- cds`, so the deletion is never committed.

Report all lint issues grouped by file, including:

Expand Down
19 changes: 9 additions & 10 deletions .claude/commands/new-chapter.md
Original file line number Diff line number Diff line change
@@ -1,33 +1,32 @@
---
description: Scaffold a new chapter in the Quarto website
description: Scaffold a new topic page in the Quarto website
allowed-tools:
- Read
- Write
- Edit
- Bash(quarto render:*)
---

Create a new chapter from `$ARGUMENTS` (e.g. `/new-chapter intro Introduction`).
Create a new topic page from `$ARGUMENTS` (e.g. `/new-chapter intro Introduction`).

First, parse `$ARGUMENTS`: the first whitespace-delimited token is the **slug**
(used for the filename, no `.qmd`); everything after the first space is the
**title** (default the title to the slug if none is given).

Steps:

1. Create `chapters/<slug>.qmd` with YAML frontmatter holding just `title:` (set
1. Create `<slug>.qmd` at the repo root with YAML frontmatter holding just `title:` (set
to the title). Do NOT set `date:` --- `_quarto-website.yml` sets
`date: last-modified` globally, and a per-page `date:` would override it and
freeze the date. Do NOT add a top-level `#` heading in the body --- Quarto
renders the frontmatter `title:` as the page heading, so a `#` heading would
duplicate it.
2. Add an entry for the new chapter to the `website.navbar.left` "Chapters" menu
in `_quarto-website.yml` (read the file first to find the menu; there is no
`chapters:` key in `_quarto.yml`). Use `text:` for the menu label and
`href: chapters/<slug>.qmd`.
Also add `chapters/<slug>.qmd` to the "Chapters" section of `website.sidebar`
in the same position: the sidebar order sets the previous/next page links.
3. Confirm it renders: `quarto render chapters/<slug>.qmd`.
2. Add `<slug>.qmd` to `website.sidebar.contents` in `_quarto-website.yml`,
in reading order: the sidebar order sets the previous/next page links.
Add the same page, in the same position, to the matching menu under
`website.navbar.left` (create the `left:` list and menu if this is the
first page): the navbar menus mirror the sidebar one-for-one.
3. Confirm it renders: `quarto render <slug>.qmd`.

Style rules (from CLAUDE.md):

Expand Down
4 changes: 2 additions & 2 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,12 @@ When describing lists of three or more items, use a bullet list instead of a com

**Examples:**

❌ **Don't** use comma-separated lists for three or more items:
**Don't** use comma-separated lists for three or more items:
```
The template includes GitHub Actions workflows for publishing, link checking, and spell checking.
```

✅ **Do** use bullet lists instead:
**Do** use bullet lists instead:
```
The template includes GitHub Actions workflows for:

Expand Down
5 changes: 2 additions & 3 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
# Keep git submodules and pinned GitHub Actions up to date automatically.
#
# This template embeds the shared `macros` repo as the `latex-macros` git submodule. Dependabot's
# This repo embeds the shared `macros` repo as the `latex-macros` git submodule. Dependabot's
# `gitsubmodule` updater opens a PR whenever a submodule's default branch
# (currently `main`) advances, so neither this template nor the books generated
# from it have to bump the pointer by hand. See the ADR at Morrison-Lab/macros
# (currently `main`) advances, so the pointer never has to be bumped by hand. See the ADR at Morrison-Lab/macros
# (docs/adr/0001-macros-submodule-versioning.md) for why we track `main` rather
# than a pinned release tag.
version: 2
Expand Down
6 changes: 2 additions & 4 deletions .github/instructions/r-and-config.instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,8 @@ description: "Use when editing R source, Quarto configuration, or GitHub workflo
- Run `Rscript -e 'spelling::spell_check_package()'` before finishing changes to
`.R` or config files. Fix only errors you introduced, and add legitimate
technical terms to `inst/WORDLIST` rather than disabling the check.
- This is a template: every dependency added here lands in every downstream
book, so do not add a new R package or Quarto extension without a clear
reason. Align workflow and render changes with the existing CI patterns
instead of inventing parallel setup.
- Align workflow and render changes with the existing CI patterns instead of
inventing parallel setup.
- Do not edit generated files (`README.md` is built from `README.Rmd`; `_site/`,
`_freeze/`, and `.quarto/` are build outputs).

Expand Down
6 changes: 2 additions & 4 deletions .github/prompts/quarto-review.prompt.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,10 @@ Review checklist:
chunk and referenced with inline R.
5. Citations and attribution are present where adapted content or factual
claims appear, and only sources in `references.bib` are used.
6. No new R package or Quarto extension is added without a clear reason (this is
a template --- every dependency lands in every downstream book).
7. No generated files are edited (`README.md` from `README.Rmd`; `_site/`,
6. No generated files are edited (`README.md` from `README.Rmd`; `_site/`,
`_freeze/`, `.quarto/`), and spell/link-check failures are fixed at the
source (wordlist or content), not suppressed.
8. The author ran the required local validation:
7. The author ran the required local validation:
- render of the touched page
- `lintr`
- `spelling::spell_check_package()`
Expand Down
4 changes: 2 additions & 2 deletions .github/rulesets/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ Applies to the default branch:
- **No force-pushes, no branch deletion.**
- **Copilot code review** runs on every push and on draft PRs.
- **No mandatory approval by default** --- PRs require no approvals to merge
(`required_approving_review_count: 0`); raise this under Settings → Rules →
(`required_approving_review_count: 0`); raise this under Settings -> Rules ->
Rulesets if you want a review gate.
- **Bypass** in `pull_request` mode for the Maintain role (role id 2) ---
Maintainers can merge pull requests even when the ruleset's PR requirements
Expand All @@ -35,7 +35,7 @@ Applies to the default branch:
## Editing the ruleset

Edit `main.json` here, then run `apply-rulesets.sh` to push the change to
the live repo. Or edit in the GitHub UI (Settings → Rules → Rulesets) and
the live repo. Or edit in the GitHub UI (Settings -> Rules -> Rulesets) and
re-export with:

```sh
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/check-bibliography-dois.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,4 @@ jobs:
check-dois:
permissions:
contents: read
uses: Morrison-Lab/gha/.github/workflows/check-bibliography-dois.yml@v2
uses: Morrison-Lab/gha/.github/workflows/check-bibliography-dois.yml@v3
4 changes: 2 additions & 2 deletions .github/workflows/check-links.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ name: Check Links
# The reusable workflow reproduces the previous standalone behavior: lychee
# link check over .qmd/.md/.html, a PR skip-label ("links checked by hand"),
# and auto-filing an issue when links break on main. We pass our repo-local
# lychee.toml so the qwt-specific excludes (e.g. quarto.org) still apply.
# lychee.toml so the cds-specific excludes (e.g. quarto.org) still apply.
# See https://github.com/Morrison-Lab/gha for inputs and versioning.

on:
Expand All @@ -26,6 +26,6 @@ jobs:
contents: read
issues: write
pull-requests: read
uses: Morrison-Lab/gha/.github/workflows/check-links.yml@v2
uses: Morrison-Lab/gha/.github/workflows/check-links.yml@v3
with:
lychee-config: lychee.toml
19 changes: 8 additions & 11 deletions .github/workflows/claude-code-review.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
# line, and already skips drafts, Dependabot bumps, fork PRs, and PRs that
# edit this workflow file itself (the action can't review its own diff --
# see gha's own comments on the App-token validation 401). This caller only
# supplies qwt's Quarto/R-specific review guidance on top.
# supplies cds's Quarto/R-specific review guidance on top.
# See https://github.com/Morrison-Lab/gha for inputs and versioning.
#
# Secrets are passed explicitly rather than via `secrets: inherit`: GitHub
Expand Down Expand Up @@ -41,15 +41,15 @@ jobs:
contents: read
pull-requests: write # post review + inline comments
issues: write
id-token: write # required by claude-code-action for the App-token exchange
actions: read # lets the reviewer read CI status
uses: Morrison-Lab/gha/.github/workflows/claude-code-review.yml@v2
actions: read # lets the reviewer read CI status (github_ci MCP server)
checks: read # required by v3 reusable workflow (gha#833)
uses: Morrison-Lab/gha/.github/workflows/claude-code-review.yml@v3
secrets:
CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} # Max-plan OAuth; empty when using API key
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} # direct API key; empty when using OAuth
SUBMODULES_TOKEN: ${{ secrets.SUBMODULES_TOKEN }} # optional; latex-macros/ is public, so unset is fine
with:
pr-number: ${{ inputs.pr_number }}
pr-number: ${{ github.event.pull_request.number || inputs.pr_number }}
checkout-submodules: true # latex-macros/ so the reviewer can read it, not report it "not initialized"
# `claude` is included because a remote Claude session pushing a
# commit directly to a PR fires `synchronize` with `claude` as the
Expand All @@ -58,9 +58,8 @@ jobs:
# "non-human actor".
allowed-bots: 'github-actions[bot],claude'
prompt-addendum: |
In addition to the standard checks above, this is the qwt Quarto
website template -- small, prose-heavy, and re-used as a template
for downstream books -- so also prioritize:
In addition to the standard checks above, this is a small,
prose-heavy Quarto website, so also prioritize:

1. **Quarto / prose correctness**
- `.qmd` syntax: chunk options use `#|` directives, fenced
Expand All @@ -73,15 +72,13 @@ jobs:

2. **R code**
- Conforms to `.lintr.R` settings (snake_case, line length, etc.).
- No new package dependencies without justification -- every
dependency added here lands in every downstream book.

3. **Build hygiene**
- No accidental commits of `_site/`, `_freeze/`, or `.quarto/`.
- `README.md` is generated; edits should go to `README.Rmd`.
- Bibliography entries in `references.bib` are well-formed BibTeX.

4. **CI / template impact**
4. **CI impact**
- Workflow changes don't break the `copilot-setup-steps.yml`
contract or the publish/preview pipelines.
- Spell-check / link-check failures are fixed at the source
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/claude.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
# posting Claude's prose reply when no code was committed, dispatching
# claude-code-review.yml on an explicit "@claude review" (including one
# absorbed late by the polling loop), and the late-comment dedup / rescue
# machinery (`<!-- claude-absorbed: ... -->` marker + 🚀 reaction) that was
# machinery (`<!-- claude-absorbed: ... -->` marker + rocket reaction) that was
# originally ported FROM this repo's own #73/#90/#95 into gha's claude.yml.
# See https://github.com/Morrison-Lab/gha for inputs and versioning.
#
Expand Down Expand Up @@ -53,12 +53,12 @@ jobs:
issues: write # so Claude can comment on issues
id-token: write
actions: write # dispatch claude-code-review.yml via `gh workflow run`
uses: Morrison-Lab/gha/.github/workflows/claude.yml@v2
uses: Morrison-Lab/gha/.github/workflows/claude.yml@v3
secrets:
CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} # Max-plan OAuth; empty when using API key
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} # direct API key; empty when using OAuth
SUBMODULES_TOKEN: ${{ secrets.SUBMODULES_TOKEN }} # optional; latex-macros/ is public, so unset is fine
WORKFLOW_TOKEN: ${{ secrets.WORKFLOW_TOKEN }} # optional; for editing .github/workflows
with:
install-quarto: true # qwt is a Quarto site; Claude may need to render .qmd files
install-quarto: true # cds is a Quarto site; Claude may need to render .qmd files
checkout-submodules: true # latex-macros/ (public; no SUBMODULES_TOKEN needed)
4 changes: 2 additions & 2 deletions .github/workflows/copilot-setup-steps.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# GitHub Copilot Setup Steps for qwt
# GitHub Copilot Setup Steps for cds
#
# This workflow configures the GitHub Copilot coding agent's environment
# by preinstalling R, Quarto, TinyTeX, and system dependencies.
Expand Down Expand Up @@ -54,7 +54,7 @@ jobs:
id: check_label
# Pass the expression result through env (GitHub-recommended) rather
# than interpolating it directly into the shell, to avoid the
# expression-injection anti-pattern in code that downstream books copy.
# expression-injection anti-pattern.
env:
EVENT_NAME: ${{ github.event_name }}
LABELS_CONTAIN_SKIP: ${{ contains(github.event.pull_request.labels.*.name, 'skip-cp-setup') }}
Expand Down
12 changes: 12 additions & 0 deletions .github/workflows/lint-changed-files.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,18 @@ jobs:
steps:
- uses: actions/checkout@v7

# `cds` is a self-referential symlink (`cds -> .`, see CLAUDE.md). Left
# in place, `all_files` below would also list every real file a second
# time under `cds/...`, and lintr's exclusions field always resolves
# through normalize_path() (see .lintr.R), which follows symlinks: a
# `cds/<changed-file>` entry in `all_files` normalizes onto the exact
# same path as the real changed file, so `setdiff(all_files,
# changed_files)` -- which only strips the real path, never its
# cds-prefixed alias -- ends up excluding the real changed file too.
# Removing the symlink up front avoids both problems.
- name: Remove self-referential cds symlink before linting
run: rm -f cds

- uses: r-lib/actions/setup-r@v2
with:
use-public-rspm: true
Expand Down
11 changes: 11 additions & 0 deletions .github/workflows/lint-project.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,17 @@ jobs:
steps:
- uses: actions/checkout@v7

# `cds` is a self-referential symlink (`cds -> .`, see CLAUDE.md) that
# lets include paths written as `cds/...` resolve when this repo
# renders standalone. lintr's own directory walk follows symlinks and
# has no config-level way to exclude it without also excluding the
# real files it aliases (normalize_path() collapses the two onto the
# same path -- see .lintr.R). Remove it before linting so lintr never
# walks into it; nothing under it is content distinct from what's
# already linted at the repo root, and this job never renders the site.
- name: Remove self-referential cds symlink before linting
run: rm -f cds

- uses: r-lib/actions/setup-r@v2
with:
use-public-rspm: true
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/preview.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,8 @@ jobs:
changed-chapters-banner: true
highlight-changes: ${{ !contains(github.event.pull_request.labels.*.name, 'no-preview-highlights') }}
docx-tracked-changes: true
# The home page plus chapters/, and the slide decks that sit beside them
# (gha's default `chapters/*.html` and `chapters/*.docx` would miss index).
# Pages render at the site root, beside their slide decks
# (gha's default `chapters/*.html` and `chapters/*.docx` would miss them).
changed-chapters-glob: '**/*.html'
docx-tracked-changes-glob: '**/*.docx'

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/summary.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,4 @@ jobs:
issues: write
models: read
contents: read
uses: Morrison-Lab/gha/.github/workflows/summary.yml@v2
uses: Morrison-Lab/gha/.github/workflows/summary.yml@v3
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
# Source HTML partials we intentionally track (not build output).
!qwt-reveal-toggle.html
!theme-picker.html
!page-navigation.html
*_files/
site_libs/

Expand Down Expand Up @@ -78,3 +77,6 @@ renv/staging/
# Lychee link checker
.lycheecache/
lychee-output/

# pak lockfile
.github/pkg.lock
13 changes: 13 additions & 0 deletions .lintr.R
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,17 @@ exclusions <- list(
`data-raw` = list(
pipe_consistency_linter = Inf
)

# NOTE: `cds` (the self-referential `cds -> .` symlink -- see CLAUDE.md) is
# NOT excluded here on purpose. lintr::lint_dir()/lint_package() resolve
# every exclusion path through normalize_path() (lintr:::normalize_exclusions()),
# which follows symlinks. Because `cds` points at the repo root, ANY
# exclusion naming it -- the directory itself, or any file path under it --
# normalizes to the exact same absolute path as the real file it aliases,
# so it silently excludes the real file too, not just the symlinked
# duplicate. There is no exclusions-list syntax that can single out the
# duplicate without also matching the original. The actual fix lives in
# the CI workflows (lint-project.yaml, lint-changed-files.yaml), which
# remove the `cds` symlink from the checkout before invoking lintr at all,
# so lintr's own directory walk never sees it and no exclusion is needed.
)
5 changes: 5 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,9 @@ the ai-config plugin), read
before starting work, and follow it alongside this file.
This file's own instructions add to those rules, and win only where they are
more specific.

Also follow the [UCD-SERG Lab Manual](https://ucd-serg.github.io/lab-manual/)
(source: <https://github.com/UCD-SERG/lab-manual>) and, for prose,
[*Principles of Scientific Writing*](https://morrison-lab.github.io/psw/)
(source: <https://github.com/Morrison-Lab/psw>).
<!-- ai-config:end -->
Loading
Loading