# Set up Curvenote publishing with GitHub Actions

You are helping the user connect this Git repository to a Curvenote site so that MyST content is previewed, checked and published automatically using the reusable workflows in `curvenote/actions`. Interview the user, agree a configuration, then write the files. This prompt is published in the Curvenote docs (CLI → Automation → GitHub Actions, "For your coding agent"); the workflow reference is the README at <https://github.com/curvenote/actions>.

## Ground rules

- Work only in the local working tree. Do not push, open pull requests, create repositories, change repository settings, or add secrets yourself. List those as steps for the user.
- Inspect before asking. Do not ask for what you can read from the repository; confirm it instead ("Your preview workflow uses venue `opensci`, kind `Article`. Keep those?").
- Ask one or two questions at a time: first the configuration (section 2) and branch, then section 3 in order. Explain each choice in a sentence of plain language and propose a default. Accept "use the defaults"; for "not sure", apply your recommended default and say so.
- Never invent the venue or kind. If the user does not know them, write a quoted placeholder (`'<VENUE>'`, `'<KIND>'`) and list it as an open item. If the user does not know the collection, omit `collection` (the site's default collection is used) and list it as an open item.
- Use values exactly as the site defines them. `kind` and `collection` are case-sensitive, so keep existing values as written.
- Before writing files, show the planned file list (including `AGENTS.md`, and `CLAUDE.md` if touched) and the key settings, and get a yes. If `publish: true` will be set, say plainly that merging these workflows will publish the current content without review.

## 1. Inspect the repository

Find:

- every `myst.yml` (and legacy `curvenote.yml`) and its `project.id`;
- folders that look like one article per subfolder (e.g. `papers/*`), and any template or example folders;
- the default branch: try `git symbolic-ref --short refs/remotes/origin/HEAD`, otherwise use the current branch and confirm it with the user;
- existing `.github/workflows/*`, especially any that already use `curvenote/actions`;
- existing `AGENTS.md` and `CLAUDE.md`.

Summarize what you found in three or four lines.

## 2. Explain the two configurations and let the user choose

**A. Single article.** The repository is one paper (one `myst.yml`, at the root or in one folder). Every pull request gets a preview build and Curvenote checks, reported in a PR comment. Every merge to the default branch submits a new version of the same submission (matched by `project.id`). Optionally that version is published immediately, with no editor review.

**B. Multiple articles (proceedings / journal monorepo).** One nominated folder (e.g. `papers/`) holds one article per subfolder, each with its own `myst.yml`. Each pull request should touch a single article folder; PRs are often from forks. Maintainers drive the process with PR labels: one label (e.g. `draft`) builds previews and checks, and another (e.g. `approved`) submits the article. An optional manual workflow republishes every article at once. This is the pattern used by `scipy-conference/scipy_proceedings`.

If the repository shape clearly fits one configuration, recommend it and say why. If Curvenote workflows already exist, say which configuration they implement and ask what the user wants to change; then ask only about settings affected by that change, and edit only what is needed.

## 3. Collect settings (in this order)

1. **Site:** `venue` (the Curvenote site name, e.g. `scipy`), `kind` (the submission kind, e.g. `article`), and `collection` only if it is not the site's default (quote numbers: `'2026'`). Empty repository: also ask for the article title.
2. **Configuration A:**
   - `path` — omit if `myst.yml` is at the root, otherwise the folder (e.g. `path: manuscript`).
   - `publish: true` on merge? Only if the token's user may publish and everyone who can merge is trusted. Default: off (editors publish in Curvenote).
   - Forks? Default no, which uses `pull_request`. If yes, the preview uses `pull_request_target` with a `draft` label gate (template A-fork below). The submit-on-merge workflow is unchanged.
3. **Configuration B:**
   - Article folder → `path: <folder>/*`. Only immediate subfolders are used, and names starting with `.` or `_` are skipped automatically.
   - Other folders to skip → `exclude:` as repository-relative paths, comma-separated (e.g. `papers/example, papers/old`). Not needed for `_`-prefixed folders.
   - ID convention → `id-pattern-regex`. A regex can enforce a prefix, not that the ID equals the folder name, so state any naming rule in the template and `AGENTS.md`. If existing IDs share a prefix, match it. Otherwise recommend `^<venue>-<collection>-[a-zA-Z0-9_-]{3,40}$` (drop `<collection>-` if there is none); SciPy uses `^scipy-2026-(?:[a-zA-Z0-9-_]{3,25})$`. IDs may contain only letters, digits, `-` and `_`, and must be unique.
   - Trigger style:
     - **Labels (default, needed for forks):** preview label `draft`, submit label `approved`.
     - **Merge means accepted (no forks):** preview on every PR (`pull_request`, no label), and submit on push to the default branch. Only changed article folders are submitted. Fork PRs get no preview with `pull_request`, because secrets are withheld.
   - `enforce-single-folder: true` (recommended on PR workflows): fail if a PR touches more than one article, or files outside the article folder.
   - A manual "publish all" workflow? It publishes every article immediately, with no review step, when someone runs it. Default yes for proceedings; skip it if the user does not want anything to publish without review.
4. **Extras:** mention them in one line and skip unless the user wants one.
   - `strict: true` — don't submit if checks fail.
   - `fonts` — Debian font packages for Typst PDFs.
   - `typst: false` with `images: false` — slim image: faster, but no PDF or image tooling.
   - `debug: true`.
   - `comment: false` — no PR comment, so `pull-requests: write` can be dropped.
   - `comment-title` — needed only when one workflow file calls draft or submit more than once. Each such job needs a distinct title, and no title may be a prefix of another.

## 4. Write the workflows

Rules for every workflow:

- Pin `@v1`. Replace `main` in the templates with the confirmed target branch.
- Always pass the secrets as `CURVENOTE: ${{ secrets.CURVENOTE_TOKEN }}` and `GITHUB: ${{ secrets.GITHUB_TOKEN }}`.
- Permissions:
  - PR-triggered (preview or submit): `contents: read` and `pull-requests: write`.
  - Submit on push or `workflow_dispatch`: `contents: write`, because it comments on the commit.
- Never set `label:` on a workflow that is not triggered by a pull request. Without a PR there are no labels, so it would never run.
- Update existing Curvenote workflows in place, keeping their file names, instead of adding duplicates. New files use the names below.

**A — `.github/workflows/curvenote-preview.yml`**

```yaml
name: Curvenote preview
on:
  pull_request:
    branches: ['main']
permissions:
  contents: read
  pull-requests: write
jobs:
  preview:
    uses: curvenote/actions/.github/workflows/draft.yml@v1
    with:
      venue: '<VENUE>'
      kind: '<KIND>'
      # path: manuscript            # if myst.yml is not at the root
    secrets:
      CURVENOTE: ${{ secrets.CURVENOTE_TOKEN }}
      GITHUB: ${{ secrets.GITHUB_TOKEN }}
```

**A-fork:** as above, but trigger on `pull_request_target`:

```yaml
on:
  pull_request_target:
    branches: ['main']
    types: [labeled, opened, synchronize, reopened]
```

Then add `label: draft` under `with:`. When `id-pattern-regex` is needed, set it in every workflow.

**A — `.github/workflows/curvenote-submit.yml`**

```yaml
name: Curvenote submit
on:
  push:
    branches: ['main']
permissions:
  contents: write
jobs:
  submit:
    uses: curvenote/actions/.github/workflows/submit.yml@v1
    with:
      venue: '<VENUE>'
      kind: '<KIND>'
      # publish: true               # only if chosen
    secrets:
      CURVENOTE: ${{ secrets.CURVENOTE_TOKEN }}
      GITHUB: ${{ secrets.GITHUB_TOKEN }}
```

**B — `.github/workflows/curvenote-preview.yml`**

```yaml
name: Curvenote preview
on:
  pull_request_target:
    branches: ['main']
    types: [labeled, opened, synchronize, reopened]
permissions:
  contents: read
  pull-requests: write
jobs:
  preview:
    uses: curvenote/actions/.github/workflows/draft.yml@v1
    with:
      venue: '<VENUE>'
      collection: '<COLLECTION>'
      kind: '<KIND>'
      path: papers/*
      id-pattern-regex: '^<venue>-<collection>-[a-zA-Z0-9_-]{3,40}$'
      enforce-single-folder: true
      label: draft
    secrets:
      CURVENOTE: ${{ secrets.CURVENOTE_TOKEN }}
      GITHUB: ${{ secrets.GITHUB_TOKEN }}
```

**B — `.github/workflows/curvenote-submit.yml`:** identical, but uses `submit.yml@v1` and `label: approved`.

**B, merge-means-accepted:** the preview uses `pull_request` and no `label`. The submit is triggered `on: push` to the default branch, with `permissions: contents: write`, the same `with:` values, and no `label` or `enforce-single-folder`.

**B — `.github/workflows/curvenote-publish-all.yml`** (optional):

- `on: workflow_dispatch` and `permissions: contents: write`;
- uses `submit.yml@v1`;
- the same venue, collection, kind, path, exclude and `id-pattern-regex`;
- plus `include-unchanged: true` and `publish: true`;
- no `label` and no `enforce-single-folder`.

## 5. Make the content match

- Every folder the workflows build needs a `myst.yml` with a `project.id` that matches the pattern. IDs are validated only for folders a run builds, i.e. folders changed by the PR or push, or all folders with `include-unchanged`. A missing ID fails only the PRs that touch that folder. Excluded and `_`-prefixed folders are never built.
- The default pattern, used when `id-pattern-regex` is not set, is `^([a-zA-Z0-9-]{36})$`: a UUID, e.g. from `uuidgen | tr A-Z a-z`. For an empty repository, create a minimal `myst.yml` (`version: 1`, `project.id`, `project.title`).
- Never change an existing `project.id` without asking: the ID links the folder to its existing Curvenote submission. If a single article already has a non-UUID ID, set `id-pattern-regex` to accept it (e.g. `'^kerr-lab-ms-2026$'`).
- B: add missing IDs only if the user agrees, following the convention. Give the template an example ID and a comment explaining how authors should name their folder and ID. Do not otherwise restructure existing configs; for example, `extends:` is optional.

## 6. Leave an AGENTS.md section

Create `AGENTS.md` at the repository root, or update it if it exists. Write the content between `<!-- curvenote-actions:start -->` and `<!-- curvenote-actions:end -->`. If those markers already exist, replace only what is between them, and never remove other content. If `CLAUDE.md` exists and does not mention `AGENTS.md`, append the line `See AGENTS.md for Curvenote publishing automation.`

The section must state, briefly:

- the configuration (A or B), each workflow file and what triggers it;
- venue, collection, kind, path, exclude, the ID pattern (and, for A, the ID itself), and each label and what it does;
- how to add a new article (B) or cut a new version (A);
- that `project.id` must never change once submitted;
- if `publish: true` is used anywhere, that it publishes without review;
- for `pull_request_target` workflows: they run with secrets on code from forks, so maintainers should review the diff before adding a label. New commits to a labelled PR re-run automatically. Edits to these workflows take effect only once merged to the target branch;
- that `CURVENOTE_TOKEN` is a repository secret holding a Curvenote API token for a user on the site, and that the workflows pin `curvenote/actions@v1`;
- where to read more: <https://github.com/curvenote/actions> and the Curvenote docs page CLI → Automation → GitHub Actions. To change the setup, re-run the prompt "Set up Curvenote publishing with GitHub Actions" from that page.

## 7. Verify and hand over

- Parse every YAML file you wrote with any YAML parser, e.g. `python3 -c 'import yaml,sys;[yaml.safe_load(open(f)) for f in sys.argv[1:]]' <files>`, or run `actionlint` if it is installed.
- Confirm that each `path` and `exclude` folder exists, and that each built `myst.yml` ID matches its pattern and is unique.
- Run `grep -rn "'<[A-Z]*>'" .github` to list any placeholders left.

Finish with a short summary of the files you changed, then a checklist containing only the items that apply:

1. **Token:** create a Curvenote API token (Curvenote account menu → API tokens) for a user on the site's team, with rights to submit, and to publish if `publish: true`. If `CURVENOTE_TOKEN` already exists, check that its user has these rights.
2. **Secret:** add the token as a repository secret named `CURVENOTE_TOKEN` (Settings → Secrets and variables → Actions, or `gh secret set CURVENOTE_TOKEN`).
3. **Labels:** create the labels used, e.g. `gh label create draft` and `gh label create approved`. Only people with triage access can apply them, and they should review fork PRs before labelling.
4. **Placeholders:** fill in the placeholder values and open items listed above.
5. **Test:** commit, push and open a test PR.
   - Configurations without labels: expect a "Curvenote Preview" comment with build and check links.
   - Label-gated configurations: merge the workflows first, then open a PR touching one article and add the preview label.
   - Note that any submit-on-push workflow also runs on the merge that adds it, so the current content is submitted at that point.
   - Empty or new repository: create the GitHub repository and push the default branch first; the workflows run only on GitHub.
