Repository navigation
Migration Pilot
Status: derived from a completed migration, not a proposal. Everything here
was exercised on riscv/riscv-performance-event-sampling, the Phase 1 pilot in
Template Upgrade Order. The worked example is
PRs #81–#86
and issue #80.
Migrating a spec repo takes six stacked pull requests: one moves chapter
prose, five are build and CI plumbing. This page supplements MIGRATION.md,
ANTORA.md and UPGRADING.md in the template — read those first. What it adds
is the running order, prompts for an AI agent, and the traps that cost real time.
Thirty minutes of recon decides how hard the migration is.
You need admin on the target repo (to enable Pages), gh authenticated, Docker
or Podman, Node 20+, and vale.
Then answer five questions, because each changes the work:
| Question | How | Why it matters |
|---|---|---|
| Do the existing tags parse? | ./scripts/release-info.sh latest |
Three-component semver (v0.6.0) does not parse. latest silently falls back to v0.0 and next proposes a version behind the real one |
| How many chapters, how big? | grep -n '^=' src/body.adoc |
Decides how many page files the split produces |
| Do cross-references cross chapters? | grep -o '<<[^>]*>>' src/*.adoc | sort | uniq -c |
Each crossing reference needs attribute indirection. Most do not cross |
| Any dangling or by-title references? | compare <<name>> uses to [[name]] anchors |
Splitting surfaces both, and a by-title reference can silently change target |
| Open PRs touching the prose? | gh pr list |
They conflict badly once the file moves. Land or close them first |
On the pilot this found five unparseable tags, three chapters, exactly one cross-chapter reference out of 43, three broken references, and one stale open PR. Every one shaped a later decision.
A .docmode file containing doc strips the ratification layer; the steps
below assume a specification.
Split on one criterion: does this PR touch spec prose? Exactly one does. Mixing prose into a 40-file infrastructure diff is what makes a migration feel overwhelming to the people who own the prose.
Each PR is based on the previous branch, so every diff shows only its own step.
| # | Branch | MIGRATION.md steps | Reviewer |
|---|---|---|---|
| 1 | arc-pdf-compliance |
1–6 | You |
| 2 | antora-dual-source |
7 | The task group |
| 3 | antora-scaffolding |
8–10 | You |
| 4 | antora-ci |
12, 13, 13a | You |
| 5 | template-docs |
— | You |
| 6 | vale-linting |
— | You |
Why this order:
- PR 1 touches no prose, so it can land while a content PR is still open, and it banks the ARC-compliant PDF even if the rest stalls.
- PR 2 is the only one the TG must read. Keep it small, prove the PDF is unchanged.
- PR 3 cannot be verified before PR 2 — no pages to build into a site.
-
PR 4 needs Pages enabled first. Its workflow runs on every push to
main, so enabling Pages afterwards leaves a failed run behind each merge. - PR 6 is last on purpose. Vale runs on every PR; tune it against the real prose before switching it on, not after the complaints arrive.
Make the stack legible to a reviewer arriving from a notification: prefix titles
[1/6]…[6/6] and open each body with the chain, the PR's position, and
Do not merge before #NN. GitHub sends no notification for title or body edits,
so this is free to add later.
These are written for Claude Code run from a clone of the target repo, one per
PR, checking the work between them. They assume the agent can read
riscv/docs-spec-template through gh.
Prompt 0 — recon. Run first; read the answer before committing to anything.
I'm migrating this repo to the latest
riscv/docs-spec-template. Before any changes, do recon and report: (1) doesscripts/release-info.shfrom the template parse this repo's existing git tags, and what dolatestandnextreturn? (2) what are the chapter-level headings in the body .adoc, and how many lines each? (3) list every<<...>>cross-reference and every[[...]]anchor, and tell me which references cross a chapter boundary, which are dangling, and which resolve by section title rather than by anchor; (4) are there open PRs touching the prose? Don't change anything yet.
Prompt 1 — ARC PDF compliance.
Do Part A (Steps 1–6) of the template's
MIGRATION.mdon a brancharc-pdf-compliance. Apply the Makefile and workflow changes by hand rather than copying upstream wholesale — the template's Makefile assumes the Part B layout we don't have yet. Preserve anything this repo changed deliberately, like a non-default container image. Then build with an explicit VERSION and DATE and verify all five of Step 6's acceptance criteria against the actual PDF, showing me the page text. Commit one step per commit, signed off.
Prompt 2 — the split. The important one.
Do Step 7 on a branch off the previous one. Structure it as three commits: first a pure
git mvof the chapter sources intomodules/ROOT/pages/with zero content change, keeping the largest chapter in the file that already has its history so blame survives; second, carve the smaller chapters out of it; third, the AsciiDoc edits — promote every heading level by one soleveloffset=+1reproduces the current structure, being careful not to touch====block delimiters. Then prove the PDF is unchanged: build the PDF on both branches,pdftotexteach, and diff. Any difference must be explained, not accepted. Also update CONTRIBUTING.md for the new layout.
Prompt 3 — Antora component.
Do Steps 8–10 on a branch off the previous one. Keep
antora.ymlminimal per the template's warning. Then actually build the site withnpm run previewagainst a local Kroki and verify: the build exits 0 with no warnings, every diagram in the source renders (count them against the source blocks), any cross-chapter reference resolves to a real cross-page link, and the cover page renders. Report the nav line numbers of the numbered chapters — I need them for the central playbook rule.
Prompt 4 — CI and publishing. Enable Pages before this merges.
Do Steps 12, 13 and 13a on a branch off the previous one. Verify locally:
make stamp-antorawith an explicit version produces valid YAML that survives pre-commit, andNO_STAMP=1 ./scripts/build-pages-site.shbuilds. Check specifically whether any pre-commit hook reformatsantora.yml— if yamlfmt rewraps it, the release-time stamp will emit invalid YAML and the automated stamp PR will fail its own checks.
Prompt 5 — documentation.
Add the template's
ANTORA.md,ARC_SUBMISSION.mdandUPGRADING.mdon a branch off the previous one.UPGRADING.mdgoes verbatim — its references to the template's own files are deliberate.ANTORA.mddescribes this repo, so localize it, and check every concrete value in it: the section-numbering line numbers, the repository layout, and the content-source example are all the template's and will be wrong here. Replace its phased-plan section with this repo's actual migration status, including known gaps. GenerateSPEC_STATE.mdfrom this repo's state rather than copying it.
Prompt 6 — Vale. Expect a judgement call here.
On a branch off the previous one, add
.vale.iniand the Vale workflow, but before committing, run Vale locally against the chapter pages and give me a breakdown by rule and severity. For each noisy rule tell me whether it is flagging a real defect or a house convention, and check what severity CI actually fails at. Don't rewrite prose to satisfy a linter without asking me.
After each prompt, check the agent's verification rather than taking its summary at face value — ask to see the diff, the page text, or the build output.
Eight problems hit the pilot. All are repo-agnostic and will recur.
1. Legacy tags silently break versioning. Three-component tags (v0.6.0)
do not parse under the two-digit scheme, so release-info.sh latest falls back
to v0.0 and next proposes v0.01 — behind the real release. Nothing
errors. Decide the first new-scheme version explicitly; do not rewrite old tags.
2. xref:page.adoc#id[] renders as a dead link in the PDF. Antora needs
that form; the PDF build turns it into the literal text see spdis.pdf. The
template's sample pages contain no cross-references, so there is no worked
example. Fix: hold the reference in an attribute with an ifndef default
carrying the PDF form (<<id>>) in the page, and the cross-page form in
antora.yml. Antora hard-sets component attributes, so it wins. Verify both.
3. By-title references bind to the first match. <<CSRs>> with two sections
named "CSRs" resolves to whichever comes first in document order. The pilot's
pointed at the wrong chapter for some time, and splitting changed the answer.
Convert by-title references to explicit anchors, and diff the rendered PDF to
see which ones moved.
4. yamlfmt breaks the release-time stamp. stamp-antora-version.sh replaces
values line by line; yamlfmt line-wraps long scalars like page-phase-notice.
Left alone, the automated stamp PR emits invalid YAML and fails its own
pre-commit check at release time. Upstream's .pre-commit-config.yaml excludes
antora.yml — take it.
5. A per-spec Vale vocabulary cannot exist. vale sync unpacks config.zip
over StylesPath/config/ and deletes sibling vocabulary directories, and CI
syncs before every lint. Terms must go upstream to riscv-admin/riscv-vale.
Until they do, disable riscv.SpellingRV with a comment saying why.
6. Vale's spacing rule fails CI at error severity. CI runs
--minAlertLevel=error. If the spec uses two spaces after a period,
riscv.Spacing reports every one as an error and fails every content PR.
Disable it; the alternative is reformatting the whole document. Its spelling
rule is only a warning, so that one is about noise, not failure.
7. Enable Pages before the publish workflow merges, and set the source
directly to "GitHub Actions". The workflow runs on every push to main, so
enabling it later leaves a failed run after each merge. enablement: true in
the workflow does not work — GITHUB_TOKEN is refused with
Resource not accessible by integration, an Actions token limit, not an org
policy. Landing on "Deploy from a branch" first queues a built-in build that can
overwrite the site with the repo root.
8. Stacked PRs suppress branch-filtered workflows. Vale's workflow is
filtered to pull_request: branches: [main], so it never runs on a stacked PR
and you cannot claim it passes. Changing the base does not re-trigger it either
— that fires edited, which is not in the default trigger set. Close and reopen
the PR to fire reopened.
One technique worth copying rather than a trap: git mv before splitting.
Rename the monolith to whichever chapter keeps most of its lines, in a commit
with no content change, then carve the smaller chapters out of it. On the pilot
that preserved direct blame for 737 of 825 lines.
Do not let a PR open on "the build passed". Each has a specific thing to prove.
PR 1 — check the PDF, not the exit code.
make VERSION=v0.8 DATE=2026-06-12
pdftotext -f 1 -l 1 build/<name>-v0.8-20260612.pdf -
Page 1 must end Version v0.8, 2026-06-12: Stabilized; page 2 is the
Document State preface; page 3 is the TOC. Confirm an untagged build names
itself with the commit sha and says it is not an ARC artifact.
PR 2 — prove the PDF did not change. The gate that matters, and it is cheap:
# on the new branch, then on the old one
make VERSION=v0.8 DATE=2026-06-12
pdftotext build/<name>-v0.8-20260612.pdf after.txt
diff before.txt after.txt
On the pilot this produced exactly two differing lines, both a corrected cross-reference. Every difference must be explained. An unexplained diff means the split changed the document. Put the result in the PR description — "identical across 1983 lines except these two, and here is why" is the most reassuring thing you can show a task group.
PR 3 — build the site and count things. npm run preview, then check the
rendered HTML: diagram count against source blocks, cross-chapter reference
resolving to a real link, cover page rendering. "No warnings" is necessary, not
sufficient.
PR 4 — exercise the release path without releasing. Run make stamp-antora
with an explicit version, confirm the YAML is valid and survives pre-commit,
then revert. Run the Pages build locally. Confirm the stamp job reports
skipping on the PR — that proves release-only gating works without cutting a
release.
PR 6 — simulate CI's actual flags. CI uses --minAlertLevel=error, stricter
than a default local run in the way that matters:
vale --minAlertLevel=error --glob='*.adoc' modules/ROOT/pages/
Compare exit codes with the disputed rules on and off. That turns "this rule is noisy" into "this rule fails every content PR".
The migration is mostly invisible to the people who own the prose, so keep the traffic low and specific. Three touchpoints:
- A tracking issue up front, with the PR list in merge order and a short "what changes for you" section: the PDF is unchanged, chapter prose moves to one file per chapter, level-0 titles, cross-references within a chapter stay. See the Migration Notice Template.
- A heads-up immediately before the split PR, with the final paths.
- The site URL once publishing lands.
Lead the author-facing note with the reassurance, not the mechanics: the PDF does not change, and here are both PDFs to check.
If an outside contributor has an open PR against the prose, tell them before you move the file, not after.
Collect these as you go rather than guessing, and put them in the tracking issue:
- Dangling cross-references — splitting makes them visibly broken on the site. Do not invent targets; the surrounding text usually hints at another document.
- Any by-title reference whose target changed, with before and after section numbers.
-
Whether an unwired bibliography or index should be connected. Absent
cite:orindexterm:macros, they render empty and were probably never intended. - The first version number under the new scheme.
Neither is a PR in the spec repo, and both are easy to forget:
-
Register with the central playbook — an issue against
riscv-admin/antora-dev.riscv.org. Check whether the repo is a content source there at all; the pilot was not, so the issue had to request the source and the numbering rule. That rule'schapters: {start, end}are line numbers innav.adoc, not chapter numbers, and adding or reordering a nav entry silently drifts site numbering until the rule is updated. -
Contribute vocabulary to
riscv-admin/riscv-valeso the spelling rule can be re-enabled. Merge terms into alphabetical position so the diff is pure additions, and measure the effect before and after.
Finally, decide explicitly whether to adopt version-bot.yml. Deferring it is
reasonable — it wants SPEC_STATE.md scaffolding and deserves its own review —
but it changes how releases are cut, so say so in the PR rather than letting
maintainers discover that workflow_dispatch no longer releases.