From d2ac9149bcfb6248cd531a0ee238e92cfb45ff74 Mon Sep 17 00:00:00 2001 From: Reid Barber Date: Fri, 5 Jun 2026 13:44:26 -0500 Subject: [PATCH] initialize spectrum-audit skill --- .../s2-docs/scripts/generateAgentSkills.mjs | 129 ++++++++++++++++++ .../spectrum-audit/checks/01-setup-config.md | 53 +++++++ .../checks/02-component-usage.md | 37 +++++ .../spectrum-audit/checks/03-styling.md | 47 +++++++ .../spectrum-audit/checks/04-accessibility.md | 30 ++++ .../spectrum-audit/checks/05-versioning.md | 18 +++ .../spectrum-audit/checks/06-testing.md | 12 ++ .../skills/spectrum-audit/report-template.md | 61 +++++++++ .../skills/spectrum-audit/scoring-rubric.md | 56 ++++++++ 9 files changed, 443 insertions(+) create mode 100644 packages/dev/s2-docs/skills/spectrum-audit/checks/01-setup-config.md create mode 100644 packages/dev/s2-docs/skills/spectrum-audit/checks/02-component-usage.md create mode 100644 packages/dev/s2-docs/skills/spectrum-audit/checks/03-styling.md create mode 100644 packages/dev/s2-docs/skills/spectrum-audit/checks/04-accessibility.md create mode 100644 packages/dev/s2-docs/skills/spectrum-audit/checks/05-versioning.md create mode 100644 packages/dev/s2-docs/skills/spectrum-audit/checks/06-testing.md create mode 100644 packages/dev/s2-docs/skills/spectrum-audit/report-template.md create mode 100644 packages/dev/s2-docs/skills/spectrum-audit/scoring-rubric.md diff --git a/packages/dev/s2-docs/scripts/generateAgentSkills.mjs b/packages/dev/s2-docs/scripts/generateAgentSkills.mjs index 8815e2e12c2..52d6c869b7a 100644 --- a/packages/dev/s2-docs/scripts/generateAgentSkills.mjs +++ b/packages/dev/s2-docs/scripts/generateAgentSkills.mjs @@ -28,6 +28,11 @@ const MARKDOWN_DOCS_DIST = path.join(REPO_ROOT, 'packages/dev/s2-docs/dist'); const MDX_PAGES_DIR = path.join(REPO_ROOT, 'packages/dev/s2-docs/pages'); const MARKDOWN_DOCS_SCRIPT = path.join(__dirname, 'generateMarkdownDocs.mjs'); const MIGRATION_REFS_DIR = path.join(REPO_ROOT, 'packages/dev/s2-docs/migration-references'); +const AUDIT_SKILL_SOURCE_DIR = path.join(REPO_ROOT, 'packages/dev/s2-docs/skills/spectrum-audit'); +const RSP_S2_SKILL_SOURCE_DIR = path.join( + REPO_ROOT, + 'packages/dev/s2-docs/skills/react-spectrum-s2' +); const WELL_KNOWN_DIR = '.well-known'; const WELL_KNOWN_SKILLS_DIR = 'skills'; @@ -79,6 +84,19 @@ const SKILLS = { author: 'Adobe', website: 'https://react-aria.adobe.com/' } + }, + 'spectrum-audit': { + name: 'spectrum-audit', + description: + 'Audit a codebase for adherence to the Spectrum design system and React Spectrum S2 best practices. Use when a developer asks to audit, review, lint, or check a project for Spectrum/S2 correctness, configuration, styling, accessibility, or component-usage issues.', + kind: 'audit', + license: 'Apache-2.0', + sourceDir: 's2', + compatibility: 'Requires a React project using @react-spectrum/s2.', + metadata: { + author: 'Adobe', + website: 'https://react-spectrum.adobe.com/' + } } }; @@ -665,6 +683,59 @@ Use these when you need more component-by-component or API-level detail: ); } +function generateAuditSkillMd(skillConfig) { + return ( + `${generateFrontmatter(skillConfig)}# Spectrum Audit + +Audit a codebase for adherence to the Spectrum design system and React Spectrum S2 best practices, then produce a scored, prioritized report. This skill is **report-only** — it does not modify any files. + +## When to use + +Use when a developer asks to audit, review, lint, or check a project for Spectrum / S2 correctness, configuration, styling, accessibility, or component-usage issues. + +Requires a project with \`@react-spectrum/s2\` installed. If S2 is not a dependency, say so and stop. + +## How it works + +Run these phases in order. + +### Phase 0 — Scope + +- Identify the project — or, in a monorepo, the specific package — to audit. Audit the package, not the workspace root. +- Detect the package manager from the lockfile, the bundler (Vite / webpack / Next.js / Parcel / Rollup / ESBuild), and read \`package.json\` dependencies. +- Confirm \`@react-spectrum/s2\` is installed. Establish the source globs to scan (e.g. \`src/**/*.{tsx,jsx,ts,js}\`). + +### Phase 1 — Run the checks + +Work through each check file in \`references/checks/\`, in order. For every violation, record a finding: \`{file:line, rule, severity, category, fix}\`. Cite only line numbers you actually read or grepped — never invent locations. + +- [01 — Setup & configuration](references/checks/01-setup-config.md) +- [02 — Component usage](references/checks/02-component-usage.md) +- [03 — Styling](references/checks/03-styling.md) +- [04 — Accessibility & correctness](references/checks/04-accessibility.md) +- [05 — Versioning & maintenance](references/checks/05-versioning.md) +- [06 — Testing](references/checks/06-testing.md) + +The canonical rules behind these checks live in [Implementation guidance](references/docs-implementation-guidance.md) and [Getting started](references/docs-getting-started.md). When you need a component's API or the canonical component list to judge a finding, use the \`react-spectrum-s2\` skill if it is installed. + +### Phase 2 — Score + +Apply the [scoring rubric](references/scoring-rubric.md) to the recorded findings to compute per-category scores and the overall Spectrum Adherence Score. The score is arithmetic over counted findings — do not estimate it. + +### Phase 3 — Report + +Write \`SPECTRUM-AUDIT.md\` to the audited project following the [report template](references/report-template.md): headline score, grade, and severity counts; a summary; scores by category; findings grouped by severity (each with a clickable \`file:line\` and a link to its check file); prioritized action items; and what looks good. + +### Phase 4 — Hand off + +This skill does not edit code. Recommend: + +- The \`react-spectrum-s2\` skill to implement the fixes. +- The \`migrate-react-spectrum-v3-to-s2\` skill if Spectrum 1 packages (\`@adobe/react-spectrum\`, \`@react-spectrum/*\`, \`@spectrum-icons/*\`) are present. +`.trimEnd() + '\n' + ); +} + /** * Copy documentation files to the skill's references directory. */ @@ -823,6 +894,52 @@ function writeMigrationReferences(skillDir, sourceDir) { ]); } +function writeAuditReferences(skillDir, sourceDir) { + const refsDir = path.join(skillDir, 'references'); + fs.mkdirSync(refsDir, {recursive: true}); + + // Copy authored audit check files. + const checksSourceDir = path.join(AUDIT_SKILL_SOURCE_DIR, 'checks'); + const checksTargetDir = path.join(refsDir, 'checks'); + fs.mkdirSync(checksTargetDir, {recursive: true}); + for (const file of fs.readdirSync(checksSourceDir)) { + if (file.endsWith('.md')) { + fs.copyFileSync(path.join(checksSourceDir, file), path.join(checksTargetDir, file)); + } + } + + // Copy the authored scoring rubric and report template. + for (const file of ['scoring-rubric.md', 'report-template.md']) { + fs.copyFileSync(path.join(AUDIT_SKILL_SOURCE_DIR, file), path.join(refsDir, file)); + } + + // Reuse the generated getting-started doc as the canonical setup reference. + copyFocusedDocs(sourceDir, skillDir, [['getting-started.md', 'docs-getting-started.md']]); + + // Reuse the canonical S2 implementation guidance and component decision tree (single source of + // truth — don't restate the rules in the audit). Resolve the {{...}} doc-link tokens to the + // co-generated react-spectrum-s2 skill's references, which share this skill's parent directory. + const guidanceReplacements = { + '{{guidesBase}}': '../react-spectrum-s2/references/guides/', + '{{componentsBase}}': '../react-spectrum-s2/references/components/', + '{{testingBase}}': '../react-spectrum-s2/references/testing/' + }; + fs.writeFileSync( + path.join(refsDir, 'docs-implementation-guidance.md'), + renderCustomMarkdown( + path.join(RSP_S2_SKILL_SOURCE_DIR, 'implementation-guidance.md'), + guidanceReplacements + ) + '\n' + ); + fs.writeFileSync( + path.join(refsDir, 'docs-component-decision-tree.md'), + renderCustomMarkdown( + path.join(RSP_S2_SKILL_SOURCE_DIR, 'component-decision-tree.md'), + guidanceReplacements + ) + '\n' + ); +} + function collectSkillFiles(skillDir) { const files = []; @@ -918,6 +1035,18 @@ function generateSkill(skillConfig, wellKnownRoot) { return skillDir; } + if (skillConfig.kind === 'audit') { + fs.writeFileSync(path.join(skillDir, 'SKILL.md'), generateAuditSkillMd(skillConfig)); + console.log(`Generated ${path.relative(REPO_ROOT, path.join(skillDir, 'SKILL.md'))}`); + + writeAuditReferences(skillDir, skillConfig.sourceDir); + console.log( + `Copied audit references to ${path.relative(REPO_ROOT, path.join(skillDir, 'references'))}` + ); + + return skillDir; + } + const isS2 = skillConfig.name === 'react-spectrum-s2'; // Parse documentation entries diff --git a/packages/dev/s2-docs/skills/spectrum-audit/checks/01-setup-config.md b/packages/dev/s2-docs/skills/spectrum-audit/checks/01-setup-config.md new file mode 100644 index 00000000000..a48a16f7f2e --- /dev/null +++ b/packages/dev/s2-docs/skills/spectrum-audit/checks/01-setup-config.md @@ -0,0 +1,53 @@ +# Check 01 — Setup & configuration + +Validates that the project is configured the way [Getting started](../docs-getting-started.md) specifies. Misconfiguration here is high-impact: the `style` macro silently no-ops, CSS bloats, or strings for 30+ languages ship to every user. + +Read first: `../docs-getting-started.md` (canonical setup), plus the project's `package.json` and bundler config file. + +## Detection inputs (gathered in Phase 0) + +- **Package manager:** lockfile — `yarn.lock`, `package-lock.json`, or `pnpm-lock.yaml`. +- **Bundler:** `vite.config.*`, `webpack.config.*`, `next.config.*`, `.parcelrc` / a `parcel` dependency, `rollup.config.*`, or an esbuild build script. +- **Installed deps:** `dependencies` / `devDependencies` in `package.json`. + +## Checks + +### `@react-spectrum/s2` is installed +- **Detect:** `@react-spectrum/s2` present in `package.json`. If absent, the audit does not apply — stop and tell the user. +- **Severity:** — (precondition) + +### Style macro plugin configured +- **Rule:** Non-Parcel bundlers must wire `unplugin-parcel-macros` into the build; Parcel needs ≥ 2.12.0. Without it, `style({...})` calls are never evaluated at build time and components render unstyled. +- **Detect:** + - Vite/Rollup/React Router/ESBuild/webpack/Next: `unplugin-parcel-macros` in deps **and** referenced in the bundler config (`macros.vite()`, `macros.webpack()`, `macros.rollup()`, `macros.esbuild()`). + - Parcel: `parcel` ≥ 2.12.0 (macros are built in) — no plugin needed. + - Next.js: also confirm the app starts with `--webpack` (not Turbopack) — check the `dev`/`build` scripts. `unplugin-parcel-macros` does not work with Turbopack. +- **Severity:** Critical. + +### CSS bundle optimization +- **Rule:** All S2 + macro-generated CSS should be combined into a single shared `s2-styles` bundle rather than code-split per route. Atomic CSS overlaps heavily between components, so loading it up front is smaller than duplicating it across chunks. +- **Detect:** + - Parcel: `@parcel/bundler-default.manualSharedBundles` with an `s2-styles` entry in the root `package.json`. + - webpack/Next: `optimization.splitChunks.cacheGroups` with an `s2`/`s2-styles` group testing for `@react-spectrum/s2` and `macro-*.css`. + - Vite/Rollup/React Router: `build.rollupOptions.output.manualChunks` returning `'s2-styles'` for `macro-*.css` and `@react-spectrum/s2/*.css`. +- **Severity:** High. + +### lightningcss minification +- **Rule:** Compile/minify CSS with lightningcss — it produces a much smaller bundle and dedupes atomic rules. +- **Detect:** `cssMinify: 'lightningcss'` (Vite/React Router) or `CssMinimizerPlugin.lightningCssMinify` (webpack/Next). +- **Severity:** Medium. + +### Locale optimization +- **Rule:** S2 ships localized strings for 30+ languages by default. Projects should install the locale optimization plugin and declare only the languages they support. +- **Detect:** `@react-aria/optimize-locales-plugin` (webpack/Vite/Rollup/React Router/ESBuild) or `@react-aria/parcel-resolver-optimize-locales` (Parcel) installed **and** configured with a `locales` list. +- **Severity:** Medium. + +### Single root Provider, wired to the router +- **Rule:** Mount one `Provider` from `@react-spectrum/s2/Provider` at the app root, wired to the client router; SSR frameworks (Next.js, React Router) set `locale` from the server request and render `Provider` with `elementType="html"`. +- **Detect:** exactly one root `Provider`; a `router={{navigate}}` prop; for Next/RR, a server-derived locale. Flag a missing `router`, or SSR setups that don't sync the locale. +- **Severity:** High. (Provider *scope* misuse — stacking, hard-coded `colorScheme` — is in [04-accessibility](04-accessibility.md).) + +### React 19 +- **Rule:** React 19 is recommended for S2. +- **Detect:** `react` version in `package.json`. +- **Severity:** Low. diff --git a/packages/dev/s2-docs/skills/spectrum-audit/checks/02-component-usage.md b/packages/dev/s2-docs/skills/spectrum-audit/checks/02-component-usage.md new file mode 100644 index 00000000000..fabef2c72e3 --- /dev/null +++ b/packages/dev/s2-docs/skills/spectrum-audit/checks/02-component-usage.md @@ -0,0 +1,37 @@ +# Check 02 — Component usage + +Validates that the project uses S2 components where they exist, builds custom components on React Aria Components + the `style` macro, and follows S2 composition and collection conventions. This is the most judgment-heavy category — read the component's docs (via the `react-spectrum-s2` skill, if installed) before flagging. + +Canonical rules: `../docs-implementation-guidance.md` (Component composition, Collections, Typography, Form fields). Reverse-lookup help ("is there an S2 component for this?"): `../docs-component-decision-tree.md`. + +## Checks + +### Use an S2 component where one exists +- **Rule:** Prefer S2 components over hand-rolled UI. S2 covers ~85 components — Button, Picker, ComboBox, Menu, Dialog/AlertDialog, Card/CardView, TableView, ListView, Tabs, and more. +- **Detect:** hand-rolled equivalents — `
`, custom dropdown/modal/tooltip implementations, native `