Skip to content
Open
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
118 changes: 118 additions & 0 deletions skills/docgen-fixup/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
---
name: docgen-fixup
description: Review an auto-generated OZ API docs PR (from any of the three `.github/workflows/generate-api-docs-*.yml` receivers — contracts, community-contracts, confidential-contracts) and fix the known regression patterns the docgen pipeline introduces on each run. Trigger when the user says "review PR <N>", "check the v5.x docs regen", "fix the regressions on the latest bot PR", or "run docgen-fixup". Scans the diff for cross-ref leaks, broken code blocks, dangling struct anchors, mangled admonitions, wrong URL rewrites, missing docs-side props, and other patterns cataloged in `references/patterns.md`, then applies direct MDX patches. Also flags NatSpec / `.adoc` mistakes that should be fixed upstream in the source contracts repo.
allowed-tools: Read, Write, Edit, Glob, Grep, Bash
inputs:
pr_number:
type: text
prompt: "Docs PR number to fix (e.g. 206)."
required: true
contracts_repo_path:
type: text
prompt: "Absolute path to the source contracts repo. Leave blank to auto-detect from the PR body's source-repo hint (matches a sibling `solidity/`, `openzeppelin-community-contracts/`, `community/`, or `openzeppelin-confidential-contracts/`)."
required: false
contracts_tag:
type: text
prompt: "Source tag / branch the PR was generated from (e.g. v5.7.0-rc.0, master). Leave blank to parse from the PR body."
required: false
mode:
type: choice
prompt: "Run mode. interactive = show findings, wait for approval per pattern. automatic = apply every fix from `references/patterns.md` and report."
options:
- interactive
- automatic
default: interactive
required: false
---

# docgen-fixup

Fix the known regression patterns in an auto-generated API docs PR before it merges. The docs bot runs three receivers — one per source repo — each on a release tag / master commit, and opens a PR against `content/<slice>/**/*.mdx`:

| Workflow | Source repo | Docs slice | Versioned |
|-----------------------------------------------------|---------------------------------------------------|--------------------------------|-----------|
| `.github/workflows/generate-api-docs-contracts.yml` | `OpenZeppelin/openzeppelin-contracts` | `content/contracts/<major>.x/` | yes |
| `.github/workflows/generate-api-docs-community-contracts.yml` | `OpenZeppelin/openzeppelin-community-contracts` | `content/community-contracts/` | no |
| `.github/workflows/generate-api-docs-confidential-contracts.yml` | `OpenZeppelin/openzeppelin-confidential-contracts` | `content/confidential-contracts/` | no |

All three run the same transformer (`docgen/templates-md/` + `scripts/convert-adoc.js` + `scripts/process-mdx.js`), so the same regression patterns apply to all three. The transformer is intentionally kept simple — it does not paper over upstream NatSpec / `.adoc` mistakes, and it does not try to be exhaustive about every edge case. Those get caught here.

## When to use

- The docs bot just opened a PR (title starts with `[CI] Update API docs for OpenZeppelin/…`).
- The user names a PR number and asks for a review / fix pass.
- Before merging any auto-generated docs update.

Do **not** use this skill for:

- Hand-written guide pages that were not touched by the bot (use a normal edit).
- Changes to `docgen/templates-md/` or `scripts/convert-adoc.js` — those are transformer changes, not fixup. Keep the transformer minimal; catalog new regression patterns here instead.

## Guiding principle

The docgen transformer stays minimal. It fixes only genuine regex / template bugs — not upstream authoring mistakes. Everything the docgen leaves broken should either be:

1. **Fixed upstream** in the source `.sol` NatSpec or `docs/modules/ROOT/pages/*.adoc`, so the next regen produces correct output — and cataloged in `references/patterns.md` for the next reviewer.
2. **Patched directly** in the generated MDX for the current PR, so the PR is correct without waiting for a regen.

Both. Upstream fixes drop the pattern from future PRs; direct patches unblock the current one.

Some patterns have **no clean upstream fix** because the value depends on a docs-side decision (e.g. which Wizard `version=` to pin for a given release). Those are `n/a` for upstream and are re-applied on every regen. That is not a bug — it is the boundary between what upstream owns and what the docs repo owns.

## Inputs

| Input | Required | Notes |
|-----------------------|----------|--------------------------------------------------------------------|
| `pr_number` | yes | PR to review (auto-generated by the bot) |
| `contracts_repo_path` | no | absolute path; auto-detected from the PR body if omitted |
| `contracts_tag` | no | git tag / ref; parsed from the PR body if omitted |
| `mode` | no | `interactive` (default) or `automatic` |

Never invent an SHA, a path, or a PR number. If auto-detection fails, stop and report which field is missing.

## Workflow

Follow `process.md` end-to-end. It:

1. Validates inputs and resolves the source repo + tag from the PR body.
2. Fetches the PR diff and lays out per-file patches under `/tmp/pr-<N>-diffs/`.
3. Runs the pattern catalog in `references/patterns.md` against each changed file.
4. Emits a findings block per pattern (interactive mode waits for approval; automatic mode applies immediately).
5. Applies direct MDX patches, opens upstream PRs where warranted, and updates the PR branch.

## Pattern catalog

`references/patterns.md` is the source of truth for what this skill knows how to fix. Each entry has:

- **Symptom** — what the reader sees in the generated MDX.
- **Detection** — the grep / heuristic that finds it.
- **Root cause** — where in the pipeline (docgen resolver / regex / template / upstream NatSpec / upstream `.adoc`) the pattern comes from.
- **Upstream fix** — the change to the source `.sol` / `.adoc` so the next regen produces correct output. Skip if there is no clean upstream fix.
- **MDX patch** — the direct patch to apply on the current PR.
- **Example** — a concrete case from a prior regen.

When you discover a **new** regression pattern that isn't in the catalog, append it to `references/patterns.md` in the same shape as an existing entry, and note it in the run report. The catalog is a living document — every reviewer contributes.

## Scope of edits

Edit only:

- Files inside the resolved PR's diff — do not touch pages the bot did not modify.
- Upstream source files under `<CONTRACTS_REPO_PATH>/contracts/**/*.sol` or `<CONTRACTS_REPO_PATH>/docs/modules/ROOT/pages/**/*.adoc` when a pattern has an upstream fix — via a **separate branch and PR** on the contracts repo, never in a direct commit to `master`.

Do **not**:

- Modify `docgen/templates-md/` or `scripts/convert-adoc.js`. If a pattern seems to demand a transformer change, stop and surface it to the user — that is a separate design decision, not a fixup.
- Touch docs slices the current PR is not scoped to. Each of the three docgen workflows targets a single slice (`content/contracts/<major>.x/`, `content/community-contracts/`, or `content/confidential-contracts/`); patch only inside that slice.
- Rewrite prose beyond what a specific pattern requires.

## End of run

Produce a compact report per `references/report-template.md`:

- Files fixed, per pattern, with line hints.
- Upstream PRs opened (URL + one-line summary each).
- Any new patterns added to `references/patterns.md`.
- Anything skipped (with the reason).

Do not write the report to a file unless the user asks — inline in the final message is enough.
172 changes: 172 additions & 0 deletions skills/docgen-fixup/process.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
# docgen-fixup process

Deterministic workflow. Steps run in order. If a step fails, stop and surface — do not skip ahead.

All paths in this document are relative to the docs repo unless prefixed `<CONTRACTS_REPO_PATH>`.

---

## Step 0 — Validate inputs

Inputs arrive from the frontmatter `inputs` schema. Validate inline:

- `pr_number`: is a positive integer; PR exists and is open on `OpenZeppelin/docs`.
- `contracts_repo_path` (if provided): exists and is a git repo.
- `contracts_tag` (if provided): resolves in the source contracts repo.

Missing / invalid input is a hard error — return an error message naming every failing field. Never invent values.

## Step 1 — Fetch PR metadata

```
gh pr view <pr_number> --repo OpenZeppelin/docs \
--json title,body,headRefName,baseRefName,changedFiles,url,mergeable
```

Record `headRefName` (branch), `baseRefName` (target — usually `main`), and the body.

## Step 2 — Resolve source repo, docs slice, and tag

Parse the PR body:

- **Repository** line names the source repo (e.g. `OpenZeppelin/openzeppelin-contracts`, `OpenZeppelin/openzeppelin-community-contracts`, `OpenZeppelin/openzeppelin-confidential-contracts`).
- **Reference** line names the tag / branch (e.g. `v5.7.0-rc.0`, `master`).
- **Output Directory** line names the docs slice (e.g. `content/contracts/5.x/api`, `content/community-contracts/api`, `content/confidential-contracts/api`).

Fill in `contracts_repo_path` and `contracts_tag` from inputs if provided; otherwise from the body. Then:

- Auto-detect the local checkout. Walk the parent of `<DOCS_REPO_PATH>` and any known sibling roots. For each candidate directory, run `git -C <candidate> remote get-url origin 2>/dev/null` and match against the parsed source repo. Common names to try, per source repo:

| Source repo | Candidate dir names |
|--------------------------------------------------------|--------------------------------------------------------|
| `OpenZeppelin/openzeppelin-contracts` | `solidity`, `openzeppelin-contracts` |
| `OpenZeppelin/openzeppelin-community-contracts` | `community`, `openzeppelin-community-contracts` |
| `OpenZeppelin/openzeppelin-confidential-contracts` | `confidential`, `openzeppelin-confidential-contracts` |

Fall back to a `git remote -v | grep <repo>` sweep across all directories under the workspace root if none of the common names hit. Do not invent a path.

- Fetch the tag: `git -C <CONTRACTS_REPO_PATH> fetch origin refs/tags/<contracts_tag>:refs/tags/<contracts_tag>` for a tag, or `git -C <CONTRACTS_REPO_PATH> fetch origin <contracts_tag>` for a branch (community-contracts and confidential-contracts often generate against `master`).
- Create a read-only worktree pinned at the ref:
```
git -C <CONTRACTS_REPO_PATH> worktree add /tmp/oz-src-<slug> <contracts_tag>
```
Use a slug that includes both the repo short-name and the ref so parallel runs don't collide: `oz-src-contracts-v5.7.0-rc.0`, `oz-src-community-master`, etc. Remove in Step 11.

Note the docs slice from the PR body so pattern-scan globs stay scoped to that slice (`content/<slice>/**/*.mdx`) and don't false-positive on other slices.

If auto-detection or fetching fails, stop with a clear error naming both the parsed repo and the candidate paths that were tried.

## Step 3 — Check out the PR branch locally

```
git -C <DOCS_REPO_PATH> fetch origin pull/<pr_number>/head:pr-<pr_number>
git -C <DOCS_REPO_PATH> checkout pr-<pr_number>
```

Record the previous branch so it can be restored at end of run.

## Step 4 — Capture per-file diffs

Stage the diff for pattern scanning:

```
mkdir -p /tmp/pr-<pr_number>-diffs
for f in $(gh pr diff <pr_number> --repo OpenZeppelin/docs --name-only); do
safe=$(echo "$f" | sed 's|/|__|g')
git -C <DOCS_REPO_PATH> diff origin/<baseRefName> pr-<pr_number> -- "$f" > "/tmp/pr-<pr_number>-diffs/$safe.diff"
done
```

## Step 5 — Run the pattern catalog

For each pattern in `references/patterns.md`, in order:

1. Run its **Detection** command / heuristic against the diff (or the PR branch's MDX, whichever the pattern specifies).
2. Collect hits — one row per (file, line, matched snippet).
3. For each hit, verify against upstream source in `/tmp/oz-src-<tag>/` before flagging. Skip if the hit is a false positive (the pattern description says how).
4. If the pattern **has** an upstream fix, note the upstream file + line for the eventual upstream PR.

## Step 6 — Findings gate (interactive mode)

Emit ONE findings block per pattern that has hits. Format:

```
## Pattern: <pattern-name>
Symptom: <one-line>
Root cause: <one-line>

### Hits
- <file>:<line> — <short snippet>
→ MDX patch: <what the fix will do>
→ Upstream: <file>:<line> — <what the upstream fix will do> (or: "no clean upstream fix")

### Approval
Reply "apply" to patch the MDX + open the upstream PR, "skip" to leave this pattern alone this run, or refine to change the plan.
```

End the turn after emitting all findings blocks. Wait for the user's response. Accept: `apply`, `apply all`, `skip`, or refinements. Anything else re-emit + wait.

In `automatic` mode, skip this gate — apply every patch immediately.

## Step 7 — Apply MDX patches

For each approved pattern:

1. Read the affected MDX file (must Read before Edit).
2. Apply the direct patch described in `references/patterns.md`. Use `Edit` with enough surrounding context to make the `old_string` unique.
3. Verify the patch: re-grep for the pattern's detection signature on the file; the hit should be gone.

Never batch a rewrite. One `Edit` per (file, hit).

## Step 8 — Open upstream PRs

For each approved pattern with an upstream fix:

1. Group hits by target file — one branch per file is fine, one branch per pattern is better.
2. Create a worktree on the source contracts repo (do not disturb the user's current checkout):
```
git -C <CONTRACTS_REPO_PATH> worktree add /tmp/oz-upstream-<slug> origin/master
```
3. Apply the upstream edits in that worktree.
4. Commit with a subject in the form `docs: <what changed> in <file>`.
5. Push to the user's fork:
```
git -C /tmp/oz-upstream-<slug> push <fork-remote> <branch>
```
The fork remote is the one whose URL points at the user's GitHub username. Do not push to `origin` on the contracts repo.
6. Open a PR:
```
gh pr create --repo <upstream-repo> --base master --head <fork-user>:<branch> \
--title "<subject>" --body "<body referencing the docs PR that surfaced this>"
```
7. Remove the worktree.

Record the PR URL for the final report.

## Step 9 — Commit and push the MDX patches

```
git -C <DOCS_REPO_PATH> add <patched-mdx-files>
git -C <DOCS_REPO_PATH> commit -m "fixup: apply docgen-fixup patterns to <pr_number>"
git -C <DOCS_REPO_PATH> push origin pr-<pr_number>:<headRefName>
```

Only push if the user's docs remote points at their fork or they have write access to `OpenZeppelin/docs`. Verify with `git remote -v` before pushing. If neither is true, stop and surface — do not force it.

## Step 10 — Catalog new patterns (if any)

If Step 5 surfaced a regression pattern that is NOT in `references/patterns.md`:

1. Draft a new entry in the same shape as an existing one (Symptom / Detection / Root cause / Upstream fix / MDX patch / Example).
2. Append it to `references/patterns.md`.
3. Commit under the same message so the catalog grows with each run.

## Step 11 — Restore local state

- Return the docs repo to the branch it was on when the run started.
- Remove `/tmp/oz-src-<tag>/`, `/tmp/oz-upstream-<slug>/`, `/tmp/pr-<pr_number>-diffs/`.
- Do not delete the `pr-<pr_number>` local branch — leave it available for follow-up.

## Step 12 — Report

Emit the report per `references/report-template.md`. Inline in the final message — do not write to a file unless the user asks.
Loading