diff --git a/.claude/skills/capture-screenshots/SKILL.md b/.claude/skills/capture-screenshots/SKILL.md new file mode 100644 index 0000000000..83f388830f --- /dev/null +++ b/.claude/skills/capture-screenshots/SKILL.md @@ -0,0 +1,90 @@ +--- +name: capture-screenshots +description: >- + Capture consistent Mergify dashboard screenshots for the docs site using + Chrome browser automation, save them to the correct images/ directory, and + emit the ready-to-paste astro:assets import and tag with alt text. Use + when adding or refreshing a dashboard/app screenshot in docs, when a docs page + needs a UI image, or when asked to screenshot app.mergify.com for + documentation. Drives the claude-in-chrome MCP tools against the user's + existing logged-in Chrome session. +--- + +# Capture Dashboard Screenshots + +Produce clean, consistent screenshots of the Mergify dashboard +(`app.mergify.com`) and wire them into a docs page. + +## Prerequisite + +The user must already be **logged into app.mergify.com in Chrome**. This skill +reuses the existing browser session through the claude-in-chrome MCP tools; it +never handles credentials. If navigation lands on a login page, stop and ask the +user to log in (`! open https://app.mergify.com`), then continue. + +## Load the browser tools first + +This skill targets the **claude-in-chrome** MCP server (a user/Claude Code-level +MCP, not declared in this repo's `.claude/mcp.json`). The tool names below are its +stable, documented interface. If you drive a different browser MCP, map each step +to that server's equivalent capability — list tabs, create a tab, navigate, +resize the window, run in-page JavaScript, read the page, capture a screenshot. + +If the `mcp__claude-in-chrome__*` tools are deferred, load them in ONE ToolSearch +call before anything else: + +``` +ToolSearch select:mcp__claude-in-chrome__tabs_context_mcp,mcp__claude-in-chrome__tabs_create_mcp,mcp__claude-in-chrome__navigate,mcp__claude-in-chrome__resize_window,mcp__claude-in-chrome__computer,mcp__claude-in-chrome__read_page,mcp__claude-in-chrome__javascript_tool +``` + +## Workflow + +1. **Get tab context** — call `tabs_context_mcp` first to see current tabs. Do + NOT reuse an old tab unless the user asks; create a fresh one with + `tabs_create_mcp`. +2. **Set a standard window size** — `resize_window` to the standard viewport + (see `references/conventions.md`). Consistency across screenshots matters more + than any single size. +3. **Navigate** — `navigate` to the target dashboard URL. Wait for the page to + finish loading (`read_page` to confirm content is present). +4. **Clean the frame** — dismiss overlays that would pollute the shot (cookie + banner, Intercom launcher, product tours). Use the dismissal snippets in + `references/conventions.md`. NEVER trigger a JS `alert`/`confirm`/`prompt` — + it freezes the extension. +5. **Capture** — take the screenshot with the `computer` tool. Frame the relevant + region; capture the specific panel rather than the whole chrome when possible. +6. **Save** — write the image to + `src/content/docs/images/
//.png` following the + naming convention. +7. **Emit the snippet** — output the ready-to-paste import + `` tag with + drafted alt text (see below). Hand it back so it can be dropped into the MDX + page (or applied directly if this was called from document-a-feature). + +## Output snippet format + +```mdx +import { Image } from "astro:assets" +import from "../../images/
//.png" + +} alt="" /> +``` + +Alt text describes what the reader sees and why it matters, not "screenshot of +dashboard". Example: `alt="Merge queue view showing two pull requests batched +together"`. + +## Details + +Read `references/conventions.md` for: the standard viewport sizes, theme +handling (light by default), the overlay-dismissal snippets, the +`section/page/name` directory convention, common dashboard URLs, and tips for +stable, repeatable framing. + +## Guardrails + +- Stop and ask if: a page won't load, you hit a login wall, or a tool errors + 2–3 times. Do not loop on a failing browser action. +- Capture a couple of extra frames if recording a flow with `gif_creator`, for + smooth playback. +- Never commit screenshots that contain another customer's private data — use the + user's own org or a demo org. diff --git a/.claude/skills/capture-screenshots/references/conventions.md b/.claude/skills/capture-screenshots/references/conventions.md new file mode 100644 index 0000000000..8b408e43ed --- /dev/null +++ b/.claude/skills/capture-screenshots/references/conventions.md @@ -0,0 +1,99 @@ +# Screenshot Conventions + +Consistency is the whole point. Use the same viewport, theme, and framing across +every docs screenshot so pages look coherent and re-captures diff cleanly. + +## Table of contents + +- [Viewport](#viewport) +- [Theme](#theme) +- [Directory & naming](#directory--naming) +- [Overlay dismissal](#overlay-dismissal) +- [Common dashboard URLs](#common-dashboard-urls) +- [Stable framing tips](#stable-framing-tips) + +## Viewport + +Use `resize_window` before capturing. Standard sizes: + +| Purpose | Size | +| --- | --- | +| Default docs screenshot | 1440 × 900 | +| Wide / full-table view | 1680 × 1050 | +| Narrow / focused panel | 1024 × 768 | + +Default to **1440 × 900** unless the content needs more width. Astro + Sharp +optimize and downscale on the site, so capture at the full standard size rather +than a cramped window — never upscale a small capture. + +## Theme + +Capture in **light theme by default** (the docs render on a light surface and the +existing screenshots are light). Only capture dark theme when the page is +specifically about a dark-mode feature, and then keep light and dark variants +side by side with matching framing. + +## Directory & naming + +Save to: + +``` +src/content/docs/images/
//.png +``` + +- `
` mirrors the docs section (`merge-queue`, `integrations`, + `ci-insights`, …). +- `` is the page slug the image belongs to (omit if the section is flat and + matches existing layout — check neighbors first). +- `` describes the content: `queue-view-batched-prs.png`, not + `screenshot-1.png`. + +Match the existing layout under `src/content/docs/images/` — look before you +create a new subfolder. + +## Overlay dismissal + +Remove chrome that pollutes the shot. Run via `javascript_tool`. These only +remove elements from the DOM — they do NOT trigger dialogs. + +```js +// Intercom launcher + messenger +document.querySelectorAll( + '.intercom-lightweight-app, #intercom-container, [class*="intercom"]' +).forEach((el) => el.remove()); + +// Cookie / consent banners (adjust selector to what is present) +document.querySelectorAll( + '[id*="cookie"], [class*="cookie-banner"], [class*="consent"]' +).forEach((el) => el.remove()); +``` + +NEVER call `alert`, `confirm`, or `prompt` from `javascript_tool` — a modal +dialog freezes the extension and ends the session. Use `console.log` for +debugging and read it back with `read_console_messages`. + +## Common dashboard URLs + +`app.mergify.com` (confirm against the live app; paths evolve): + +| Area | Path | +| --- | --- | +| Org dashboard | `/github/` | +| Merge queues | `/github//queues` | +| CI Insights | `/github//ci-insights` | +| Test Insights | `/github//tests` | +| Settings | `/github//settings` | + +If a path 404s, navigate from the dashboard UI and read the resulting URL with +`read_page` rather than guessing. + +## Stable framing tips + +- Wait until data has loaded (no spinners) before capturing — `read_page` to + confirm real content is present. +- For re-captures (freshness updates), reuse the exact same viewport and URL so + the new image lines up with the one it replaces. +- Prefer capturing a specific panel/region over the entire browser chrome; it + keeps the reader focused and survives unrelated UI changes. +- Avoid capturing personal account names or other customers' data — use the + user's own org or a demo org. diff --git a/.claude/skills/docs-gap-analysis/SKILL.md b/.claude/skills/docs-gap-analysis/SKILL.md new file mode 100644 index 0000000000..bbf8f5594e --- /dev/null +++ b/.claude/skills/docs-gap-analysis/SKILL.md @@ -0,0 +1,92 @@ +--- +name: docs-gap-analysis +description: >- + Find undocumented or stale Mergify features by comparing what the product + shipped against what the docs cover. Use when asked what is missing from the + docs, to audit docs coverage, to find undocumented features, to check whether + recent features/PRs are documented, or to plan documentation work. Gathers + shipped-feature signal from merged PRs, Linear, and the changelog, maps it + against src/content/docs/, and produces a prioritized gap report that feeds the + document-a-feature skill. +--- + +# Docs Gap Analysis + +Docs rot silently: the product ships, the docs don't follow, and nobody notices +until a customer does. This skill surfaces the gap so it can be closed. + +The output is a prioritized report. Each item is shaped to hand straight to the +**document-a-feature** skill. + +## Workflow + +Create a todo for each step. + +1. **Set the window.** Default to the last 90 days unless the user gives a range. + Convert to absolute dates. + +2. **Gather shipped-feature signal.** Read `references/signal-sources.md` for + exactly where to look and which tools to use. In short, pull from: + - **Changelog** (primary) — `src/content/changelog/` entries: the curated, + confirmed list of announced user-facing changes. Highest signal per token. + Read-only — NEVER edit these files. + - **Merged PRs** (supplementary) in `Mergifyio/monorepo` via `gh` — to catch + unannounced changes and get a specific feature's exact surface. The monorepo + is too active to enumerate fully; spot-check, don't sweep. + - **Linear** — completed issues/projects via the Linear MCP tools, for the + "why" framing. + +3. **Build a feature inventory.** Distill the signal into discrete, user-facing + capabilities. Drop pure refactors, internal-only changes, and bug fixes that + don't change documented behavior. For each capability note: name, the surface + it touches (config key / action / CLI / API / dashboard), and the source link. + +4. **Map against the docs.** For each capability, search `src/content/docs/`: + - `grep -ri "" src/content/docs/` + - Check whether the schema-driven references already cover new config/CLI + options (`OptionsTable`, `CliCommand` pull from the live schema, so a new + key may already render in a table while its prose context is missing). + +5. **Classify each capability:** + - **Undocumented** — no meaningful coverage anywhere. + - **Partial** — mentioned but missing setup, options, or context. + - **Stale** — documented, but the behavior described predates a change (the + PR/changelog says it changed; the docs still describe the old behavior). + - **Covered** — adequately documented (omit from the action list, count only). + +6. **Produce the report.** Prioritize by reader impact: customer-facing config / + merge-queue / protections behavior first; niche or advanced last. For each gap + give: capability, classification, the source link, the target docs page (or + "new page in `
/`"), and a one-line suggested action. Consider pushing + the report to the hublot kiosk for review. + +## Report format + +``` +## Docs Gap Report — + +### Undocumented (N) +- — src: + → suggest: new page in
/ (use document-a-feature) + +### Partial (N) +- — missing on + → suggest: extend + +### Stale (N) +- describes old behavior; changed in + → suggest: update + +### Covered: N capabilities (no action) +``` + +## Guardrails + +- Read-only on `src/content/changelog/` — it's a signal source, never an edit + target. +- Don't flag internal/refactor PRs as doc gaps. Only user-facing capability + changes count. +- Be honest about uncertainty: if you can't tell whether something is documented, + say "needs review" rather than asserting a gap. +- The output is a plan, not edits. To actually write a page, hand the item to + document-a-feature. diff --git a/.claude/skills/docs-gap-analysis/references/signal-sources.md b/.claude/skills/docs-gap-analysis/references/signal-sources.md new file mode 100644 index 0000000000..ac9fa7d760 --- /dev/null +++ b/.claude/skills/docs-gap-analysis/references/signal-sources.md @@ -0,0 +1,94 @@ +# Signal Sources + +Where to find "what the product shipped", and how to query each source. + +## Table of contents + +- [Changelog (primary)](#changelog-primary) +- [Merged PRs (supplementary)](#merged-prs-supplementary) +- [Linear](#linear) +- [The docs themselves](#the-docs-themselves) +- [Schemas](#schemas) + +## Changelog (primary) + +Start here. `src/content/changelog/` is the curated, confirmed list of +user-facing changes — every entry is an announced capability with a date, tags, +and a one-line description. It is the highest-signal source per token and the +practical primary for gap analysis. (It is autogenerated and **must never be +edited** — read only.) + +```bash +# Recent changelog entries by filename date +ls src/content/changelog/ | sort -r | head -40 +``` + +Treat each entry as a capability and map it against the docs. An entry whose +linked docs page doesn't reflect it is a strong stale/partial candidate. The +residual blind spot is a user-facing change that shipped but was not announced in +the changelog — catch those with the supplementary PR sweep below. + +When an entry asserts a behavior change (e.g. a changed default), confirm it +against the live schema before editing docs — the two can disagree (the schema +is synced separately and may lag, or the default field may differ from runtime). +If they conflict, flag it rather than guessing. See [Schemas](#schemas). + +## Merged PRs (supplementary) + +The product lives in `Mergifyio/monorepo` (engine + dashboard ship together). +Use this to catch changes the changelog missed. + +> Volume warning: the monorepo is extremely active. A single `gh pr list` page +> (max ~250 PRs) may only span ~2 weeks, so a full 90-day PR-by-PR sweep is not +> feasible. Use PRs to spot-check recent activity and to get the exact surface of +> a specific feature — not as the primary enumeration. Use the GitHub CLI; no +> local checkout needed. + +```bash +# Merged PRs in a window (adjust date) +gh pr list --repo Mergifyio/monorepo --state merged --limit 200 \ + --search "merged:>=2026-04-01" \ + --json number,title,labels,mergedAt,url + +# Inspect one PR's user-facing surface +gh pr view --repo Mergifyio/monorepo --json title,body,files,labels +``` + +Focus on PRs whose title/body/labels indicate user-facing change: new config +keys, new actions, new CLI commands/flags, new dashboard features, changed +defaults or behavior. Ignore pure refactors, dependency bumps, internal tooling, +and bug fixes that don't change documented behavior. + +## Linear (optional) + +If the Linear MCP tools are configured, use them (`list_issues`, +`list_projects`, `get_issue`) to pull **completed** issues and shipped projects +in the window. If Linear MCP is not available, skip it or work from the Linear +UI / an export — the changelog already covers confirmed surface, so Linear is a +nice-to-have for the "why" framing, not a hard dependency. Linear is best for the +"why"; PRs are best for the exact surface. A Linear project often maps to several +PRs. + +## The docs themselves + +To check coverage: + +```bash +grep -ri "" src/content/docs/ +``` + +Remember the schema-driven components: a new config key may already appear in an +`OptionsTable` (because it renders from the live schema) while having no prose +explanation. "Appears in a generated table" is not the same as "documented" — +look for the explanatory context, not just the key. + +## Schemas + +The committed schemas show the current full surface area; diffing intent against +them helps spot keys/commands with no prose: + +- `public/mergify-configuration-schema.json` — all config keys/models +- `public/cli-schema.json` — all CLI commands/flags +- `public/api-schemas.json` — API endpoints + +A key present in the schema but absent from any prose page is a coverage gap. diff --git a/.claude/skills/document-a-feature/SKILL.md b/.claude/skills/document-a-feature/SKILL.md new file mode 100644 index 0000000000..3f6c7f9d7b --- /dev/null +++ b/.claude/skills/document-a-feature/SKILL.md @@ -0,0 +1,125 @@ +--- +name: document-a-feature +description: >- + End-to-end workflow for documenting a Mergify feature on the docs site. Use + when asked to document a feature, write a new docs page, document a PR or + Linear ticket, or add/update a page under src/content/docs/. Orchestrates + placement, component selection, writing, site plumbing (navItems + redirects + + SEO), and validation (proofread pipeline, config-example validation, build + check). Composes with mdx-documentation, proofread-*, capture-screenshots, and + validate-config-examples skills. +--- + +# Document a Feature + +The spine for turning a feature into a correct, fully-wired docs page. Follow the +checklist in order. Do not skip the plumbing or validation steps — a page that is +not in `navItems.tsx` is invisible, and a page that fails `pnpm check` cannot ship. + +This skill orchestrates other skills. Invoke them with the Skill tool when the +checklist points to them; do not duplicate their content here. + +## Checklist + +Create a todo for each step and complete them in order. + +1. **Gather context** — Understand the feature. +2. **Place the page** — Decide section, new-vs-edit, and URL path. +3. **Choose components** — Map content to the right components. +4. **Write the page** — Draft MDX following conventions. +5. **Wire the plumbing** — navItems, redirects, SEO. +6. **Add visuals** — Screenshots and diagrams if needed. +7. **Validate** — Proofread pipeline, config validation, build check. + +## Step 1 — Gather context + +Identify the source of truth for the feature: + +- **A PR**: `gh pr view --json title,body,files` (use the product repo + `Mergifyio/monorepo` for engine/dashboard changes). Read the diff to learn the + real config keys, CLI flags, and UI surfaces — never invent them. +- **A Linear ticket**: use the Linear MCP tools if they are configured; + otherwise read the issue from the Linear UI or have the user paste its + acceptance criteria. +- **A description**: work from what the user gave you; ask only if a fact you + cannot derive (exact key name, plan availability) is missing. + +Establish: what the feature does, who uses it, the exact config/CLI/API surface, +and any plan/tier gating. + +## Step 2 — Place the page + +Read `references/placement-guide.md` for the full section map and decision rules. + +Decide: + +- **Which section** under `src/content/docs/` the feature belongs to. +- **New page vs. edit existing** — search for existing coverage first + (`grep -ri "" src/content/docs/`). Extending an existing page is + usually better than adding a thin new one. +- **The URL path** — match sibling naming (kebab-case, no `.mdx` in links). + +## Step 3 — Choose components + +Read `references/component-decision-table.md`. It maps each kind of content +(config reference, CLI reference, callouts, video, integration logo, CI upload +steps, interactive tables) to the right component, with import paths and props. + +Prefer the schema-driven components (`OptionsTable`, `ActionOptionsTable`, +`CliCommand`) over hand-written tables — they stay in sync with the product +automatically. + +## Step 4 — Write the page + +Invoke the **mdx-documentation** skill for frontmatter, imports, heading +hierarchy, callouts, and the complete-page example. + +While drafting, already obey the **proofread-style** rules (no em dashes, no +"let's" openers, no corporate jargon, assume the reader knows CI/Git/PRs) so the +later proofread pass is a check, not a rewrite. + +Required frontmatter: `title` and `description`. The `description` doubles as the +SEO meta description and OpenGraph description — write it for a human searching, +~120–155 characters. + +## Step 5 — Wire the plumbing + +A page is not done until it is reachable. See `references/placement-guide.md` for +exact snippets. + +1. **Navigation** — add a `NavItem` to `src/content/navItems.tsx` at the correct + nesting level, with `title`, `path`, and an `icon` (lucide / octicon / + simple-icons / mergify namespace — match siblings). +2. **Redirects** — if you renamed or moved a path, add a 301 line to + `public/_redirects` (`/old/path /new/path 301`). Never break an existing URL. +3. **SEO** — confirm the `description` is present and a reasonable length. + +## Step 6 — Add visuals + +- **Screenshots** of the Mergify dashboard: invoke the **capture-screenshots** + skill. It produces consistent images in the right `images/` subdir plus the + ready-to-paste `astro:assets` import and `` tag with alt text. +- **Diagrams** (lifecycles, flows): use Graphviz `dot` code blocks — see + mdx-documentation for the styled template. + +## Step 7 — Validate + +Run these before claiming the page is done: + +1. **Proofread pipeline** — if total changed/added docs lines ≥ 10, spawn the 4 + proofread subagents in parallel per the repo CLAUDE.md (`proofread-style`, + `proofread-technical`, `proofread-structure`, `proofread-consistency`) on the + diff. +2. **Config examples** — if the page contains any YAML/`.mergify.yml` snippets, + invoke the **validate-config-examples** skill. +3. **Build check** — run `pnpm check` (astro check + eslint + biome). Fix any + errors. For a final SSG sanity check, `pnpm build`. + +## Hard rules + +- **Never** create, edit, or delete files in `src/content/changelog/` — those are + autogenerated externally. +- **Never** hardcode image paths or use raw `![]()` markdown for images — use + `astro:assets` ``. +- **Never** add an H1 in body content — the title becomes the H1. +- Match existing patterns over inventing new ones. diff --git a/.claude/skills/document-a-feature/references/component-decision-table.md b/.claude/skills/document-a-feature/references/component-decision-table.md new file mode 100644 index 0000000000..82d3cf0233 --- /dev/null +++ b/.claude/skills/document-a-feature/references/component-decision-table.md @@ -0,0 +1,144 @@ +# Component Decision Table + +Which component to reach for, by content type. All live in `src/components/`. +Import with the `~/components/...` alias or a relative path. React components are +`.tsx`, Astro components are `.astro`. + +## Table of contents + +- [Quick map](#quick-map) +- [Config & reference tables (schema-driven)](#config--reference-tables-schema-driven) +- [Callouts](#callouts) +- [Media](#media) +- [Integration & CI pages](#integration--ci-pages) +- [Layout & navigation](#layout--navigation) +- [Specialized visualizations](#specialized-visualizations) + +## Quick map + +| You are documenting… | Use | +| --- | --- | +| Config options for a model/action | `OptionsTable` / `ActionOptionsTable` | +| A CLI command | `CliCommand` | +| A note / warning / tip | `:::note` / `:::tip` / `:::caution` / `:::danger` (Aside) | +| A dashboard screenshot | `` from `astro:assets` (see capture-screenshots skill) | +| A YouTube walkthrough | `Youtube` | +| An integration page header | `IntegrationLogo` | +| CI report upload steps | `MergifyCIUploadStep` / `BuildkiteCIUploadStep` / `MergifyCliUploadStep` | +| A product overview card grid | `DocsetGrid` + `Docset` | +| A call-to-action button | `Button` | +| A lifecycle/flow diagram | Graphviz `dot` code block (see mdx-documentation) | + +## Config & reference tables (schema-driven) + +Prefer these over hand-written tables — they render from the live schema and +never go stale. + +```mdx +import OptionsTable from "~/components/OptionsTable.tsx" +import ActionOptionsTable from "~/components/ActionOptionsTable.tsx" + + + +``` + +- `OptionsTable` `def`: a model name from the config schema (e.g. + `PullRequestRuleModel`, `QueueRuleModel`). +- `ActionOptionsTable` `def`: an action model (e.g. `QueueActionModel`, + `LabelActionModel`). +- To find valid `def` names, look at `public/mergify-configuration-schema.json` + or existing usages: `grep -rh "OptionsTable def=" src/content/docs/`. + +CLI reference card from `public/cli-schema.json`: + +```mdx +import CliCommand from "~/components/CliCommand.astro" + + +``` + +## Callouts + +Use directive syntax (renders via `Aside.astro`). Content indented 2 spaces. + +```mdx +:::note + Neutral context the reader should know. +::: + +:::tip + A best practice or shortcut. +::: + +:::caution + A risk or gotcha. +::: + +:::danger + Destructive or irreversible action. +::: +``` + +## Media + +```mdx +import { Image } from "astro:assets" +import shot from "../../images/
//.png" + +What the screenshot shows +``` + +```mdx +import Youtube from "~/components/Youtube.astro" + + +``` + +All article `` get lightbox zoom automatically (`ImageZoom`); add class +`no-zoom` to opt out. + +## Integration & CI pages + +```mdx +import IntegrationLogo from "~/components/IntegrationLogo.astro" +import logo from "../../images/integrations//logo.svg" + + +``` + +CI upload steps (props vary — check the component source for the exact prop, e.g. +`reportPath`): + +- `MergifyCIUploadStep.astro` — GitHub Actions YAML step +- `BuildkiteCIUploadStep.astro` — Buildkite pipeline step +- `MergifyCliUploadStep.astro` — CLI-based upload +- `CIInsightsSetupNote.astro`, `CliInstall.astro` — reusable setup snippets + +## Layout & navigation + +```mdx +import DocsetGrid from "~/components/DocsetGrid.astro" +import Docset from "~/components/Docset.astro" + + + + Keep your main branch green. + + +``` + +`Button.astro` props: `href`, `variant` (`primary|secondary|ghost|solid`), +`target` (default `_blank`), `icon`, `colorScheme`, `rel`. + +## Specialized visualizations + +These are bespoke to specific topics — reuse them only on their topic pages, and +read the component source before using: + +- `GitGraph.astro`, `StackMapping.astro`, `StacksLocalModel.astro` — stacks +- `ScopesDetection.astro` — merge-queue scopes +- `MergeQueueCalculator/` (React) — interactive queue calculator +- `AcademyCallout.astro` — link to Merge Queue Academy + +If unsure whether a specialized component fits, grep for where it is already used: +`grep -rl "ComponentName" src/content/docs/`. diff --git a/.claude/skills/document-a-feature/references/placement-guide.md b/.claude/skills/document-a-feature/references/placement-guide.md new file mode 100644 index 0000000000..4fb4d4f47d --- /dev/null +++ b/.claude/skills/document-a-feature/references/placement-guide.md @@ -0,0 +1,111 @@ +# Placement & Plumbing Guide + +How to place a new docs page and wire it into the site. + +## Table of contents + +- [Section map](#section-map) +- [New page vs. edit existing](#new-page-vs-edit-existing) +- [Naming and paths](#naming-and-paths) +- [Navigation (navItems.tsx)](#navigation-navitemstsx) +- [Redirects (_redirects)](#redirects-_redirects) +- [SEO](#seo) + +## Section map + +Top-level sections under `src/content/docs/`: + +| Section | Put here | +| --- | --- | +| `merge-queue/` | Queue behavior: setup, modes, rules, lifecycle, priority, batches, scopes, performance, monitoring, deploy | +| `merge-protections/` | Branch protection, auto-merge, builtin/custom protections, freezes | +| `workflow/` | Rule syntax + the `actions/` family (assign, backport, comment, label, merge, queue, rebase, review, …) | +| `configuration/` | Config reference: file-format, conditions, data-types, sharing | +| `commands/` | GitHub comment commands (queue, dequeue, rebase, squash, backport, copy, update, refresh) | +| `ci-insights/` | CI analytics setup (GitHub Actions, Buildkite, Jenkins) | +| `test-insights/` | Test framework integrations (pytest, Jest, Go, Rust, RSpec, Cypress, …) | +| `monorepo-ci/` | Monorepo CI guides | +| `stacks/` | Stacked PRs: concepts, setup, workflow, adoption | +| `integrations/` | Third-party integrations (GitHub, Datadog, Slack, Dependabot, Terraform, …) | +| `api/`, `cli/` | API and CLI usage + reference | +| `enterprise/`, `migrate/`, `security/`, `billing/`, `support/`, `browser-extensions/` | As named | + +Images mirror this layout under `src/content/docs/images/
/`. + +## New page vs. edit existing + +Search before creating: `grep -ri "" src/content/docs/`. + +Prefer **extending an existing page** when the feature is an option, sub-behavior, +or variant of something already documented. Create a **new page** only when the +feature is a distinct concept a reader would look for on its own. A thin new page +that duplicates context is worse than a new section on an existing page. + +## Naming and paths + +- File: `src/content/docs/
/.mdx` +- URL path: `/
/` (no `.mdx`, no trailing slash in links) +- Match sibling naming style exactly (look at the directory first). + +## Navigation (navItems.tsx) + +`src/content/navItems.tsx` is a hierarchical `NavItem[]`. Each entry: + +```tsx +{ title: 'Priority', path: '/merge-queue/priority', icon: 'lucide:traffic-cone' } +``` + +Nested sections use `children`, and the first child is usually an `Overview` +pointing at the section root: + +```tsx +{ + title: 'Scopes', + path: '/merge-queue/scopes', + icon: 'lucide:network', + children: [ + { title: 'Overview', path: '/merge-queue/scopes', icon: 'lucide:lightbulb' }, + { title: 'File Patterns', path: '/merge-queue/scopes/file-patterns', icon: 'lucide:file' }, + ], +}, +``` + +Rules: + +- Add the new entry next to its logical siblings, not at the end. +- Pick an `icon` from the same namespaces siblings use: `lucide:*`, `octicon:*`, + `simple-icons:*` (for branded tools), or `mergify:*` (product icons). +- `id` is auto-generated (uuid v5) — do not set it. +- The sidebar renders automatically from this file; no other nav edit is needed. + +## Redirects (_redirects) + +`public/_redirects` uses Netlify syntax, one rule per line: + +``` +/old/path /new/path 301 +``` + +When to add a redirect: + +- You **renamed or moved** a page (old URL must keep working). +- A path has a common alias or an old shape people link to. + +Patterns in use: both bare and trailing-slash forms are listed, and `:splat` is +used to forward subtrees: + +``` +/conditions /configuration/conditions 301 +/conditions/ /configuration/conditions 301 +/conditions/* /configuration/conditions/:splat 301 +``` + +Never delete an existing redirect target without providing a new one. + +## SEO + +- `title` + `description` frontmatter drive the page ``, meta description, + OpenGraph, and the auto-generated OG image (`getOgImageUrl()`). +- Write `description` for a human searching: specific, ~120–155 chars, no filler. +- Optional frontmatter: `canonicalURL` (only if the canonical differs from the + page URL). diff --git a/.claude/skills/validate-config-examples/SKILL.md b/.claude/skills/validate-config-examples/SKILL.md new file mode 100644 index 0000000000..3ba097b0de --- /dev/null +++ b/.claude/skills/validate-config-examples/SKILL.md @@ -0,0 +1,83 @@ +--- +name: validate-config-examples +description: >- + Validate Mergify YAML configuration examples embedded in the docs against the + real Mergify schema, so broken config snippets never ship. Use after editing + docs pages that contain .mergify.yml / YAML examples, when reviewing a docs PR + with config snippets, or when asked to check/validate config examples in the + docs. Extracts YAML code fences, classifies them, and validates full Mergify + configs with `mergify config validate` (optionally via the mergify:mergify-config + skill when that plugin is installed). +--- + +# Validate Config Examples + +Mergify config snippets in the docs are the most-copied content on the site. A +broken example erodes trust instantly. This skill finds every YAML example and +validates the ones that are real Mergify configs. + +## Workflow + +1. **Pick the scope.** + - Reviewing a change: run against the changed MDX files only. + - Auditing: run against the whole `src/content/docs/` tree. + +2. **Extract and classify** with the bundled script: + + ```bash + .claude/skills/validate-config-examples/scripts/extract_yaml.py <files-or-dir> + ``` + + It prints a JSON array of every YAML fence with `file`, `line`, + `classification`, and `code`. Classifications: + + | Classification | Meaning | Action | + | --- | --- | --- | + | `mergify-config` | A real Mergify config (has top-level keys like `queue_rules`, `pull_request_rules`, `merge_protections`) | **Validate it** | + | `github-actions` | A CI workflow YAML, not Mergify config | Skip | + | `partial` | Marked `# partial` or a fragment (no top-level key) | Skip validation | + | `unknown` | YAML that fits none of the above | Eyeball manually | + +3. **Validate each `mergify-config` snippet** with `mergify config validate` — + the authoritative validator (it understands condition syntax and action + options, not just the JSON shape). Write each snippet to a temp file and pass + it with `--config-file` (the command auto-detects `.mergify.yml` from the + current directory otherwise, so the flag is required for a temp file): + + ```bash + printf '%s\n' "$CODE" > /tmp/mergify-example.yml + mergify config validate --config-file /tmp/mergify-example.yml + ``` + + If the **mergify:mergify-config** plugin skill is installed, you can invoke it + instead of calling the CLI directly — it wraps the same command. As a + structural fallback when the CLI is unavailable, validate against + `public/mergify-configuration-schema.json` (JSON Schema). + +4. **Report** each failure as `file:line — <error>`. Fix clear errors directly in + the MDX (wrong key name, bad indentation, invalid condition). For anything + ambiguous, flag rather than guess — a wrong "fix" to a config example is worse + than a flagged one. + +## The fragment convention + +Many doc examples are partial on purpose — they show one rule, not a whole file. +A bare fragment cannot be validated standalone and will false-positive. + +The script treats a snippet as `partial` (and skips it) when: + +- it has no unindented top-level key (it is clearly a fragment), or +- its first lines contain a `# partial` marker comment. + +When you intentionally show a fragment that the script can't auto-detect, add a +leading `# partial` comment to the code block so it is skipped cleanly. Do not add +the marker to examples that are meant to be complete, valid configs — those should +validate. + +## Scope & guardrails + +- Only validates YAML/`yml` fences; ignores all other languages. +- Skips GitHub Actions and other non-Mergify YAML automatically. +- Never edits `src/content/changelog/`. +- Fix only genuine errors; leave valid examples alone even if you'd phrase the + surrounding prose differently. diff --git a/.claude/skills/validate-config-examples/scripts/extract_yaml.py b/.claude/skills/validate-config-examples/scripts/extract_yaml.py new file mode 100755 index 0000000000..24a2deb675 --- /dev/null +++ b/.claude/skills/validate-config-examples/scripts/extract_yaml.py @@ -0,0 +1,149 @@ +#!/usr/bin/env python3 +"""Extract YAML/yml fenced code blocks from MDX docs and classify each. + +Pure stdlib. Does NOT validate — it locates every YAML snippet, records its +file:line, and classifies it so the caller knows which snippets to feed to +`mergify config validate` and which to skip. + +Usage: + extract_yaml.py <file_or_dir> [<file_or_dir> ...] + +If a directory is given, all .mdx files under it are scanned recursively. +Outputs a JSON array to stdout: + [{"file", "line", "lang", "classification", "code"}, ...] + +Classifications: + mergify-config -> a Mergify config; validate it with `mergify config validate` + github-actions -> a CI workflow, not Mergify config; skip + partial -> explicitly marked `# partial` or a fragment; skip validation + unknown -> YAML that is none of the above; review manually +""" + +import json +import os +import re +import sys + +FENCE_RE = re.compile(r"^([ \t]*)(`{3,}|~{3,})([^\n`]*)$") + +# Top-level keys that mark a full Mergify configuration file. +MERGIFY_TOP_KEYS = { + "queue_rules", + "pull_request_rules", + "merge_protections", + "commands_restrictions", + "merge_queue", + "shared", + "defaults", + "extends", + "partition_rules", + "priority_rules", +} + +# Signals that a YAML block is a GitHub Actions / CI workflow, not Mergify config. +CI_SIGNALS = ("runs-on:", "uses:", "jobs:", "steps:") + + +def lang_of(info_string): + """First token of a fence info string, lowercased (e.g. 'yaml title=...').""" + token = info_string.strip().split()[0] if info_string.strip() else "" + # Strip a leading '{' from formats like ```{yaml} + return token.lstrip("{").rstrip("}").lower() + + +def classify(code): + lines = code.splitlines() + stripped = [ln for ln in lines if ln.strip() and not ln.lstrip().startswith("#")] + + # Explicit author marker wins. + for ln in lines[:3]: + if re.match(r"^\s*#\s*partial\b", ln, re.IGNORECASE): + return "partial" + + text = "\n".join(lines) + if any(sig in text for sig in CI_SIGNALS) and "on:" in text: + return "github-actions" + + top_keys = set() + for ln in lines: + m = re.match(r"^([A-Za-z_][\w-]*):", ln) + if m: + top_keys.add(m.group(1)) + if top_keys & MERGIFY_TOP_KEYS: + return "mergify-config" + + # No unindented top-level key at all -> a fragment (e.g. a single rule shown + # inline). Treat as partial: not independently validatable. + if stripped and all(ln.startswith((" ", "\t", "-")) for ln in stripped): + return "partial" + + return "unknown" + + +def extract_from_file(path): + out = [] + with open(path, encoding="utf-8") as fh: + lines = fh.readlines() + + i = 0 + n = len(lines) + while i < n: + m = FENCE_RE.match(lines[i].rstrip("\n")) + if not m: + i += 1 + continue + indent, fence, info = m.group(1), m.group(2), m.group(3) + lang = lang_of(info) + fence_line = i + 1 # 1-based + # Find closing fence: same marker char, at least as long, no info string. + body = [] + j = i + 1 + closed = False + while j < n: + cm = FENCE_RE.match(lines[j].rstrip("\n")) + if cm and cm.group(2)[0] == fence[0] and len(cm.group(2)) >= len(fence) and not cm.group(3).strip(): + closed = True + break + body.append(lines[j].rstrip("\n")) + j += 1 + if lang in ("yaml", "yml"): + # Strip the common fence indentation from the body. + code = "\n".join(ln[len(indent):] if ln.startswith(indent) else ln for ln in body) + out.append( + { + "file": path, + "line": fence_line, + "lang": lang, + "classification": classify(code), + "code": code, + } + ) + i = j + 1 if closed else j + return out + + +def iter_targets(args): + for arg in args: + if os.path.isdir(arg): + for root, _, files in os.walk(arg): + for f in files: + if f.endswith(".mdx"): + yield os.path.join(root, f) + elif arg.endswith(".mdx"): + yield arg + + +def main(argv): + if not argv: + print(__doc__, file=sys.stderr) + return 2 + results = [] + for path in iter_targets(argv): + results.extend(extract_from_file(path)) + json.dump(results, sys.stdout, indent=2) + sys.stdout.write("\n") + return 0 + + +if __name__ == "__main__": + sys.exit(main(sys.argv[1:]))