SPEC-0021: Docusaurus Skill Page Generation
Overviewโ
Formalizes ๐ ADR-0029 by defining how the docs-site build pipeline auto-generates a per-skill Docusaurus page and a hero-tile index from each skills/\{name\}/SKILL.md (the same file Claude Code loads at runtime), with an opt-in editorial override hatch protected by a build-time SHA-256 pin. This spec extends SPEC-0004 (Documentation Site Generation): it adds a third transform (transform-skills.js) alongside the existing ADR and OpenSpec transforms, a skillsSidebar and "Skills" navbar entry, and a /skills/ route group. It also requires installation and configuration of @docusaurus/plugin-client-redirects so the staged migration of docs-site/content/guides/commands.mdx can preserve every external inbound link.
The spec defines: (1) the SKILL.md โ MDX schema and section-ordering algorithm (canonical sections in fixed positions, non-canonical H2 sections appended verbatim in source order); (2) the skills/_index.json manifest schema and bidirectional consistency check; (3) the override file format and the Governing-SKILL: <path>@<sha256> pin; (4) silent vs. fail-build behavior for every edge case enumerated in ๐ ADR-0029; (5) governing-comment aggregation, dedup, and pill rendering; (6) the staged commands.mdx migration and per-anchor redirect requirements; (7) integration into the existing build-docs.js orchestrator without any change to .github/workflows/deploy-docs.yml.
This is a docs-site spec: there is no auth surface, no user input, and no JavaScript executed beyond what the existing Docusaurus pipeline already mitigates. The security-by-default injection from SPEC-0016 does not apply; see design.md ยง Security Posture for the rationale.
Requirementsโ
Requirement: Per-Skill Page Generationโ
The docs-site build pipeline MUST emit exactly one MDX file per directory under skills/ containing a SKILL.md. The output path MUST be docs-generated/skills/\{name\}.mdx and MUST resolve at the route /skills/\{name\} after Docusaurus build. The transform script docs-site/scripts/transform-skills.js MUST own this generation and MUST NOT be inlined into other transforms.
Scenario: every skill produces one pageโ
- WHEN
npm run buildruns indocs-site/andskills/contains directoriesadr/,work/,graph/, andprime/, each with aSKILL.md - THEN
docs-generated/skills/MUST containadr.mdx,work.mdx,graph.mdx,prime.mdx, andindex.mdx - AND the deployed routes
/skills/adr,/skills/work,/skills/graph,/skills/prime, and/skills/MUST all resolve
Scenario: editing one SKILL.md regenerates only that pageโ
- WHEN the contents of
skills/work/SKILL.mdchange andnpm run buildruns - THEN
docs-generated/skills/work.mdxMUST reflect the change - AND the other skill MDX files MUST be byte-identical to the prior build (modulo any unrelated transforms)
Requirement: Source-File Schema Mappingโ
transform-skills.js MUST extract the following inputs from each skill and place them at the documented output positions:
- Frontmatter
nameโ page H1, sidebar label, URL slug. - Frontmatter
descriptionโ page subtitle, hero-tile description,<meta>description. - Frontmatter
argument-hintโ "Usage" code block. - Frontmatter
allowed-toolsโ collapsed "Required Tools" detail block. - Frontmatter
disable-model-invocation(when present and truthy) โ a "Manual-Invocation Only" badge near the page title. - Any other frontmatter key โ ignored for rendering, preserved in the source file.
- Body intro paragraph (text after H1, before the first H2) โ "Overview" section.
- Body
## Processsection โ "Process" section, with header levels demoted by one so the page has a single H1. - Body
## Rulessection โ "Rules" section, header levels demoted by one. - Any other H2 section in the SKILL.md body โ appended verbatim (modulo
mdx-escape.js) in source order between Rules and Reference, header levels demoted by one. - The H2 extractor MUST be fence-aware: H2 lines (
## ...) that appear inside a fenced code block (delimited by```or~~~) MUST NOT be hoisted as top-level skill sections, and MUST be preserved verbatim within their enclosing fence. Implementations MUST track fence state using theisCodeFencehelper exported bydocs-site/scripts/transform-utils.js(the same helpertransformSpecReferencesandtransformAdrReferencesalready use). - Sibling
references/*.mdfiles in the skill directory โ "Reference" appendix, one collapsible<details>per file. evals/triggers/\{name\}.jsonentries withshould_trigger: trueโ "Example Invocations" code block, capped at 5 entries (the first 5 in file order).- All
<!-- Governing: ... -->and<!-- Implements: ... -->comments anywhere in the file (excluding YAML frontmatter) โ "Governing Artifacts" pill list rendered above the Overview.
Scenario: frontmatter fields populate the canonical positionsโ
- WHEN
skills/adr/SKILL.mdhas frontmattername: adr,description: "Create a new Architecture Decision Record (ADR)...",argument-hint: "[topic]", andallowed-tools: [Read, Write, AskUserQuestion] - THEN
docs-generated/skills/adr.mdxMUST render an H1 derived fromname, a subtitle line containing the description, a "Usage" code block containing/sdd:adr [topic], and a collapsed details element listingRead, Write, AskUserQuestion
Scenario: a skill with disable-model-invocation: trueโ
- WHEN
skills/\{name\}/SKILL.mdfrontmatter containsdisable-model-invocation: true - THEN the generated page MUST display a "Manual-Invocation Only" badge near the title
- AND the absence of the field (or a falsey value) MUST omit the badge
Scenario: H2 lines inside a fenced code block are not hoisted as sectionsโ
- WHEN
skills/adr/SKILL.mdcontains a## MADR TemplateH2 followed by a fenced```markdownblock whose body includes the inner H2 lines## Context and Problem Statement,## Decision Drivers,## Considered Options,## Decision Outcome,## Pros and Cons of the Options,## Architecture Diagram, and## More Information - THEN the generated
adr.mdxMUST treat## MADR Templateas a single non-canonical H2 section whose body contains the entire fenced block verbatim - AND the seven inner H2 lines MUST NOT produce additional top-level skill sections in the output
- AND the seven inner H2 lines MUST appear inside the rendered code fence exactly as authored, with no header demotion or hoisting applied to lines inside the fence
Requirement: Section Orderingโ
The generated page MUST emit sections in this fixed order: (1) H1 Title, (2) Subtitle, (3) "Governing Artifacts" pill list (if any), (4) Usage, (5) Required Tools, (6) Overview, (7) Process, (8) Rules, (9) every non-canonical H2 from the source file in source order, (10) Reference, (11) Example Invocations. Canonical sections (Title, Subtitle, Usage, Required Tools, Overview, Process, Rules, Reference, Example Invocations, Governing Artifacts) MUST appear at fixed positions; non-canonical H2 sections MUST be appended verbatim in source order between Rules and Reference; no other position is allowed for them.
Scenario: a skill with non-canonical H2 sections renders them in source orderโ
- WHEN
skills/adr/SKILL.mdbody contains## Process,## MADR Template,## Architecture Diagram,## Graph Edge Frontmatter, and## Rulesin that source order - THEN the generated
adr.mdxMUST emit Title โ Subtitle โ Governing Artifacts โ Usage โ Required Tools โ Overview โ Process โ Rules โ MADR Template โ Architecture Diagram โ Graph Edge Frontmatter โ Reference โ Example Invocations - AND the non-canonical H2s MUST appear in their original source order (MADR Template before Architecture Diagram before Graph Edge Frontmatter)
Scenario: missing canonical Process or Rules sectionโ
- WHEN
skills/\{name\}/SKILL.mddoes not contain a## Processor## RulesH2 - THEN the generated page MUST omit the corresponding section
- AND the build MUST succeed
- AND non-canonical H2s and the Reference / Example Invocations sections MUST still render
Requirement: Hero-Tile Index Pageโ
transform-skills.js MUST also emit docs-generated/skills/index.mdx rendering a <SkillTile> per registered skill. Each tile MUST carry the skill's name, description (truncated to ~140 chars at a word boundary), and argument-hint, linking to /skills/\{name\}. Tiles MUST be grouped and ordered exclusively by skills/_index.json; per-skill frontmatter MUST NOT influence grouping or order.
Scenario: hero tiles render in manifest orderโ
- WHEN
skills/_index.jsondeclares a groupCreating Artifactswith skills[adr, spec]and a groupImplementationwith skills[work, review] - THEN
docs-generated/skills/index.mdxMUST render the Creating Artifacts group before the Implementation group - AND within each group the tiles MUST appear in the order listed in the manifest
Scenario: tile description truncationโ
- WHEN a skill's
descriptionexceeds 140 characters - THEN the tile MUST display the truncated description ending at a word boundary, suffixed with an ellipsis
- AND the per-skill page subtitle MUST display the full untruncated description
Requirement: Manifest Schema and Validationโ
skills/_index.json MUST conform to the JSON Schema at docs-site/scripts/schemas/skills-index.schema.json. The manifest MUST be an object whose keys are group display names (strings) and whose values are ordered arrays of skill name strings. transform-skills.js MUST validate the manifest with Ajv at the start of the build. Schema violations MUST fail the build with the specific Ajv error message. The schema MUST forbid duplicate skill names across or within groups.
Scenario: manifest fails Ajv validationโ
- WHEN
skills/_index.jsoncontains a group whose value is a string instead of an array - THEN the build MUST fail with the Ajv error identifying the offending key and the expected array type
Scenario: duplicate skill across groupsโ
- WHEN
skills/_index.jsonlistsadrin bothCreating ArtifactsandImplementation - THEN the build MUST fail with an error naming
adrand both group keys
Requirement: Bidirectional Manifest Consistencyโ
The set of skill names in skills/_index.json MUST equal the set of subdirectories of skills/ containing a SKILL.md. A skill present on disk but absent from the manifest MUST fail the build with an error naming the unregistered skill. A manifest entry referencing a non-existent skills/\{name\}/SKILL.md MUST fail the build with an error naming the stale entry.
Scenario: a skill directory exists but is not registeredโ
- WHEN
skills/foo/SKILL.mdexists butfoodoes not appear in any group ofskills/_index.json - THEN the build MUST fail with the error:
skills/foo: not registered in skills/_index.json โ add it to a group or remove the directory
Scenario: a manifest entry has no corresponding SKILL.mdโ
- WHEN
skills/_index.jsonlistsbarin a group butskills/bar/SKILL.mddoes not exist - THEN the build MUST fail with the error:
skills/_index.json references "bar" but skills/bar/SKILL.md does not exist
Requirement: Governing-Comment Aggregation and Cross-Linkingโ
transform-skills.js MUST scan the entire SKILL.md body (frontmatter excluded) for <!-- Governing: ... --> and <!-- Implements: ... --> comments, parse each into ADR-XXXX and SPEC-YYYY references, deduplicate by reference, and sort the deduped set with ADRs ascending followed by SPECs ascending. The result MUST render as a single "Governing Artifacts" pill list at the top of the page (above Overview, below Subtitle). ADR pills MUST link to /decisions/\{adr-slug\} via transformAdrReferences from transform-utils.js; SPEC pills MUST link to /specs/\{spec-slug\}/spec#spec-NNNN (the SPEC-ID anchor only) via transformSpecReferences from transform-utils.js. The REQ "..." clause that may follow a SPEC-ID inside a <!-- Governing: --> or <!-- Implements: --> comment MAY be retained as the pill's display text, but the URL fragment MUST remain the SPEC-ID anchor โ REQ-level anchoring is not currently produced by transformSpecReferences and is deferred (see Open Questions in design.md). <!-- Implements: ... --> comments MUST be folded into the same pill list as <!-- Governing: ... --> comments โ no separate section.
Scenario: multiple comments collapse to a single pill listโ
- WHEN
skills/work/SKILL.mdcontains five<!-- Governing: -->comments (file-scope and inline within## Process) referencing ๐ ADR-0017 twice, ๐ ADR-0020 once, ๐ ADR-0015 once, SPEC-0015 four times, and SPEC-0014 once - THEN the rendered page MUST display one "Governing Artifacts" section above Overview
- AND the section MUST contain pills in this order: ๐ ADR-0015, ๐ ADR-0017, ๐ ADR-0020, SPEC-0014, SPEC-0015
- AND each ADR pill MUST link to the ADR's
/decisions/...page viatransformAdrReferences - AND each SPEC pill MUST link to
/specs/\{slug\}/spec#spec-NNNN(lowercased SPEC-ID anchor) viatransformSpecReferences, regardless of whether the source comment included aREQ "..."clause
Scenario: <!-- Implements: --> comments fold into the same listโ
- WHEN a SKILL.md contains both
<!-- Governing: <a href="/decisions/ADR-0023-frontmatter-dag-and-graph-skill" className="rfc-ref">๐ ADR-0023</a> -->and<!-- Implements: <a href="/specs/init-and-priming/spec#spec-0018" className="rfc-ref">SPEC-0018</a> REQ "Validate" --> - THEN the pill list MUST contain both references
- AND there MUST NOT be a separate "Implements" section
Scenario: skill with no governing or implements commentsโ
- WHEN
skills/\{name\}/SKILL.mdcontains zero<!-- Governing: -->and zero<!-- Implements: -->comments - THEN the generated page MUST omit the Governing Artifacts section entirely
- AND the build MUST succeed without warning
Requirement: Example Invocations from Eval Triggersโ
When evals/triggers/\{name\}.json exists, transform-skills.js MUST read the file, select up to the first 5 entries with should_trigger: true, and render them as an "Example Invocations" code block on the per-skill page. When the file is missing or contains zero entries with should_trigger: true, the section MUST be omitted silently.
Scenario: skill with curated triggers renders up to 5โ
- WHEN
evals/triggers/work.jsoncontains 9 entries withshould_trigger: true - THEN the generated
work.mdxMUST render an "Example Invocations" section containing the first 5 of those entries - AND the build MUST succeed
Scenario: skill with no eval triggers renders no sectionโ
- WHEN
evals/triggers/foo.jsondoes not exist for a registered skillfoo - THEN the generated
foo.mdxMUST omit the "Example Invocations" section entirely - AND the build MUST succeed without warning
Requirement: Override File Format and Pinโ
If skills/\{name\}/page.override.mdx exists, transform-skills.js MUST copy it verbatim to docs-generated/skills/\{name\}.mdx and skip auto-generation for that skill. The override MUST contain a header pin of the form \{/* Governing-SKILL: skills/\{name\}/SKILL.md@<sha256-of-SKILL.md-bytes> */\} as the first non-blank line. The hash MUST be the SHA-256 hex digest of the raw byte content of skills/\{name\}/SKILL.md. transform-skills.js MUST recompute that hash at build time and compare it to the pinned value.
Scenario: matching pin uses the overrideโ
- WHEN
skills/work/page.override.mdxexists with aGoverning-SKILLpin whose hash matches the currentskills/work/SKILL.mdbyte content - THEN the generated
work.mdxMUST be the verbatim contents ofpage.override.mdx - AND none of the auto-generated sections (Process, Rules, etc.) MUST be merged into the output
Requirement: Override Pin Mismatch and Helperโ
A mismatch between the pinned hash and the recomputed hash MUST fail the build with an error naming the override file path, the expected (pinned) hash, the current hash, and the remediation: re-review the override against the new SKILL.md and either run npm run docs:refresh-overrides to update the pin or delete the override to fall back to auto-generation. An override file that exists without a Governing-SKILL pin header MUST fail the build with an error pointing at the same helper. An override file whose corresponding skills/\{name\}/SKILL.md does not exist MUST fail the build with an "orphan override" error.
The helper script npm run docs:refresh-overrides MUST rewrite the pin in place to the current SKILL.md hash. The helper MUST be invoked manually by the author after they have re-reviewed the override.
Scenario: stale pin fails the buildโ
- WHEN
skills/work/page.override.mdxcarries aGoverning-SKILLpin whose hash no longer matches the currentskills/work/SKILL.md - THEN the build MUST fail with an error naming the override file, the expected hash, and the current hash
- AND the error MUST instruct the author to run
npm run docs:refresh-overridesor delete the override
Scenario: override missing pin headerโ
- WHEN
skills/foo/page.override.mdxexists but does not contain aGoverning-SKILL: ...@<sha>header - THEN the build MUST fail with an error naming the override and pointing at
npm run docs:refresh-overrides
Scenario: orphan overrideโ
- WHEN
skills/foo/page.override.mdxexists butskills/foo/SKILL.mddoes not - THEN the build MUST fail with an "orphan override" error naming
skills/foo/page.override.mdx
Scenario: refresh helper updates the pinโ
- WHEN the author runs
npm run docs:refresh-overridesafter re-reviewingskills/work/page.override.mdx - THEN the helper MUST rewrite the
Governing-SKILLheader in place with the SHA-256 of the currentskills/work/SKILL.md - AND the helper MUST NOT modify any other line of the override
Requirement: Pipeline Integrationโ
docs-site/scripts/build-docs.js MUST invoke transform-skills.js after transform-openspecs.js (so spec mappings are available to governing-comment cross-links) and before generate-graph.js (so generated skill pages can later participate in the artifact graph). transform-skills.js MUST NOT introduce new external runtime dependencies beyond Ajv (already permitted for manifest validation). .github/workflows/deploy-docs.yml MUST NOT require modification by this spec; the existing npm run build step MUST pick up the new transform automatically on the next push to main.
Scenario: orchestrator orderโ
- WHEN
npm run buildruns - THEN
transform-skills.jsMUST run aftertransform-openspecs.jscompletes - AND MUST run before
generate-graph.jsbegins
Scenario: deploy workflow unchangedโ
- WHEN this spec's implementation lands on
main - THEN
.github/workflows/deploy-docs.ymlMUST NOT be modified by the implementation PR - AND the workflow's existing
npm run buildstep MUST produce a deployable site that includes/skills/and/skills/\{name\}routes
Requirement: Routing, Sidebar, and Navbarโ
The implementation MUST add a skillsSidebar to docs-site/sidebars.ts listing the hero-tile index at /skills/ and one entry per skill at /skills/\{name\} in the order defined by skills/_index.json. docs-site/docusaurus.config.ts MUST register a "Skills" navbar entry positioned between "Guides" and "ADRs". The route prefix /skills/ MUST resolve to the hero-tile index page; the route /skills/\{name\} MUST resolve to the per-skill page.
Scenario: navbar orderโ
- WHEN the deployed site renders the navbar
- THEN the entries MUST appear in the order: Guides, Skills, ADRs (with any other entries unchanged in their existing positions)
Scenario: sidebar reflects manifest orderโ
- WHEN
skills/_index.jsonis reordered (a skill moves between groups, or its position within a group changes) - THEN the next build MUST produce a
skillsSidebarmatching the new order - AND the rendered sidebar MUST display skills in the new order
Requirement: MDX Safetyโ
transform-skills.js MUST run all generated content through mdx-escape.js before writing the output file, including the verbatim non-canonical H2 sections and the verbatim Reference appendix bodies. Override files copied verbatim MUST NOT be re-escaped (the author owns the override and is responsible for valid MDX).
Scenario: non-canonical body with curly bracesโ
- WHEN a skill body contains a literal
$\{HOME\}token outside any code fence - THEN the generated MDX MUST escape the curly braces so MDX v3 does not interpret them as a JSX expression
- AND the rendered page MUST display
$\{HOME\}literally
Requirement: Migration of commands.mdx (Step 1 โ Coexist)โ
The implementing PR for this spec MUST install @docusaurus/plugin-client-redirects as a dev dependency of docs-site/, register it in docusaurus.config.ts with an empty redirects array (or a stub configured for the eventual Step 3 anchor mapping), and leave docs-site/content/guides/commands.mdx in place. After this PR ships, both /guides/commands and /skills/\{name\} MUST resolve.
Scenario: coexisting routesโ
- WHEN the implementing PR for this spec is merged
- THEN
/guides/commandsMUST continue to resolve - AND
/skills/and/skills/\{name\}MUST also resolve - AND
package.jsonofdocs-site/MUST list@docusaurus/plugin-client-redirectsas a dependency
Requirement: Migration of commands.mdx (Step 2 โ Audit and Redirect)โ
A follow-up PR MUST replace the body of docs-site/content/guides/commands.mdx with a single redirect-style page pointing at /skills/, audit and update every inbound reference (docusaurus.config.ts navbar/footer entries, project-root README.md, project-root CLAUDE.md, the marketplace listing in .claude-plugin/plugin.json and any external marketplace metadata, prior posts in docs-site/blog/, and any links discovered by Docusaurus's broken-link checker), and configure @docusaurus/plugin-client-redirects with one entry per fragment anchor present in the deleted-in-Step-3 commands.mdx, mapping /guides/commands#\{anchor\} โ /skills/\{anchor\}.
Scenario: per-anchor redirects existโ
- WHEN the Step 2 PR is merged and a user navigates to
/guides/commands#work - THEN the browser MUST land on
/skills/workvia@docusaurus/plugin-client-redirects - AND the same redirect MUST exist for every fragment anchor present in
commands.mdxat HEAD when Step 3 deletes the file
Scenario: inbound reference audit completenessโ
- WHEN the Step 2 PR is merged
- THEN
docusaurus.config.ts,README.md,CLAUDE.md,.claude-plugin/plugin.json, and posts underdocs-site/blog/MUST contain no references to/guides/commands(only/skills/...links) - AND
npm run buildMUST emit no broken-link warnings for the rewritten references
Requirement: Migration of commands.mdx (Step 3 โ Delete)โ
A later PR (after at least one release cycle) MUST delete docs-site/content/guides/commands.mdx. The implementing PR MUST first enumerate the actual fragment-anchor set from HEAD of commands.mdx at the time of removal and confirm that @docusaurus/plugin-client-redirects already contains a redirect entry for each anchor. The PR MUST NOT rely on Docusaurus's built-in 404 fallback as a substitute for explicit per-anchor redirects.
Scenario: deletion preserves external inbound linksโ
- WHEN the Step 3 PR is merged and a user follows an external link to
/guides/commands#audit(e.g., from an old PR description) - THEN the browser MUST land on
/skills/auditvia the configured redirects - AND the user MUST NOT see a 404
Scenario: missing redirect blocks deletionโ
- WHEN the Step 3 PR omits a redirect entry for an anchor that exists in
commands.mdx - THEN the PR review MUST block the merge until the redirect entry is added
Out of Scopeโ
- The actual implementation of
transform-skills.js(separate work, planned by/sdd:plan <a href="/specs/init-and-priming/spec#spec-0021" className="rfc-ref">SPEC-0021</a>). - Adding skills as first-class nodes in the artifact graph (
/sdd:graph); skills participate today only via the governing-comment pills surfaced on their pages. - Internationalization of generated skill pages.
- Versioned skill docs.
- Embedding live screenshots, GIFs, or video assets in generated pages โ overrides remain the escape hatch for any such authored prose.
- Search-index tuning beyond what Docusaurus provides out of the box.
Related Artifactsโ
Direct relationships declared in YAML frontmatter (per ADR-0023 / SPEC-0018). Run /sdd:graph chain SPEC-0021 for the transitive view.