11# Contributing to AI Documentation Configuration
22
3- This guide explains how to configure the AI documentation system by adding new routes in the ` config.ts ` file.
3+ This guide explains how to configure the AI documentation system by adding new routes in the ` ai-docs. config.tsx ` file.
44
55## Overview
66
@@ -11,7 +11,7 @@ The AI documentation system uses a bidirectional mapping configuration that serv
1111
1212## Configuration Structure
1313
14- The main configuration is in ` config.ts ` and follows this structure:
14+ The main configuration is in ` ai-docs. config.tsx ` and follows this structure:
1515
1616``` typescript
1717export const aiDocsConfig: AiDocsConfig = {
@@ -398,3 +398,139 @@ The transformation ensures that:
3983982 . System reverse-lookups the configuration
3993993 . Finds matching route with ` serve.at ` or route key (through the [ txt] ( /apps/docs/app/txt/ ) router)
4004004 . Serves the corresponding generated content
401+
402+ ## Skills
403+
404+ On top of the AI docs, this pipeline publishes an [ agent Skill] ( https://www.npmjs.com/package/skills )
405+ at ` https://hopper.workleap.design/.well-known/skills ` , installed with
406+ ` npx skills add https://hopper.workleap.design ` .
407+
408+ Skill generation is a ** second stage that composes ` dist/ai-docs ` ** — it never renders MDX again.
409+ ` docs#build:skills ` therefore declares ` docs#build:ai-docs ` as a Turbo dependency, so one command
410+ is enough:
411+
412+ ``` bash
413+ pnpm build:skills # ~1s once the AI docs are cached; re-run freely
414+ ```
415+
416+ ` docs#build ` also depends on ` docs#build:skills ` , which is what makes the single
417+ ` turbo run build --filter=./apps/docs ` command in ` netlify.toml ` publish the skill.
418+
419+ Output lands in ` dist/skills ` and is copied to ` public/agent-skills ` (gitignored, like
420+ ` public/ai-docs ` ).
421+
422+ ### Files
423+
424+ | File | Role |
425+ | ----------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- |
426+ | ` skills.config.ts ` | What the skill contains. The only file most changes touch. |
427+ | ` skillsTypes.ts ` | Entry types and their guards. |
428+ | ` templates/skills/hopper/** ` | Hand-authored markdown: ` SKILL.header.md ` , workflows, and the guides that have no generated equivalent. |
429+ | ` skill-scripts/** ` | Sources for the scripts bundled into the skill. |
430+ | ` ../scripts/buildSkills.ts ` | Orchestrator. |
431+ | ` ../scripts/ai-utils/{copySkillFiles,generateSkillIndex,generateSkillManifest,bundleSkillScripts}.ts ` | The stages it runs. |
432+ | ` ../scripts/checkSkillsConfig.ts ` | Runs as ` pnpm --filter=docs test ` ; see below. |
433+
434+ ### Entry types
435+
436+ ` skills.config.ts ` ` files ` accepts three shapes, distinguished by which key is present:
437+
438+ ``` ts
439+ // copy — `from` is a glob under dist/ai-docs. A `to` ending in "/" keeps the source path
440+ // relative to the glob's literal prefix, so nested matches do not collapse together.
441+ { from : " /components/usage/*.md" , exclude : [" /components/usage/component-list.md" ], to : " references/components/" }
442+
443+ // copyTemplate — a hand-authored file, copied verbatim
444+ { copyTemplate : " /ai-pipeline/templates/skills/hopper/workflows/build-app.md" , to : " references/workflows/build-app.md" }
445+
446+ // merge — a hand-authored template followed by generated content
447+ { template : " /ai-pipeline/templates/skills/hopper/guides/validation-rules.md" ,
448+ merge : [" /styled-system/escape-hatches.md" ], to : " references/guides/validation-rules.md" }
449+ ```
450+
451+ ` SKILL.md ` is ` templates/skills/hopper/SKILL.header.md ` plus a generated "Documentation structure"
452+ index built from what actually landed on disk. Section descriptions come from each file's first
453+ paragraph, or from ` description ` in the config. Use ` style: "names" ` for long tails such as
454+ components — a bullet per file would triple the size of ` SKILL.md ` .
455+
456+ ### Scripts share code with the MCP server
457+
458+ The skill's three scripts are ** not** reimplementations. Each bundles a service straight out of
459+ ` apps/mcp-server ` with esbuild, so a logic change there reaches both surfaces at the next build:
460+
461+ | Script | MCP tool | Bundled from |
462+ | ---------------------------------- | ---------------------- | ------------------------------- |
463+ | ` scripts/validate-hopper-code.mjs ` | ` validate_hopper_code ` | ` src/services/validatorService ` |
464+ | ` scripts/search-tokens.mjs ` | ` get_design_tokens ` | ` src/services/tokensService ` |
465+ | ` scripts/search-icons.mjs ` | ` get_icons ` | ` src/services/iconsService ` |
466+
467+ The entry points in ` skill-scripts/ ` are thin CLI wrappers — argument parsing and output
468+ formatting only. Keep them that way; anything resembling business logic belongs in the service, so
469+ both surfaces get it.
470+
471+ Two things are swapped at bundle time, via ` aliases ` in ` skills.config.ts ` :
472+
473+ - ` @docs/ai ` and the ` env ` module (` dataAliases ` ) → the services read the skill's own ` references/ `
474+ instead of a deployed docs folder. This is why the skill mirrors the AI docs layout there.
475+ - ` @typescript-eslint/parser ` → ` validatorParser.ts ` , only for the validator. Bundling the real
476+ parser would pull in ` typescript ` , several times the whole skill budget.
477+
478+ To verify parity after touching a service, run the script and the matching MCP tool on the same
479+ input and diff them.
480+
481+ ### The skill ships as one archive
482+
483+ ` buildSkills.ts ` writes both the loose files under ` dist/skills/hopper/** ` _ and_ a single
484+ ` dist/skills/hopper.tar.gz ` , and ` index.json ` advertises only the archive, using the 0.2.0
485+ discovery schema with a sha256 digest.
486+
487+ That is deliberate. The ` skills ` CLI's per-file path (` fetchLegacySkillByEntry ` ) fires one ` fetch `
488+ per advertised file with ** no concurrency limit** and swallows every failure — ` catch {} ` returns
489+ ` null ` and the file is silently skipped, with no error and no retry. At this skill's size that
490+ reliably loses files: measured 263-317 of 326 across runs, with ` scripts/ ` missing every time
491+ because it sorts last and holds the largest files. Nothing in the output tells you.
492+
493+ With the archive the client does one request and verifies a digest, so an install is either
494+ complete or a visible failure. It is also ~ 10x less to transfer (0.52 MB compressed vs 4.8 MB).
495+
496+ The loose files stay published because they are handy to ` curl ` and cost nothing extra. If you
497+ change what the skill contains, the archive and digest regenerate automatically — never hand-edit
498+ ` index.json ` .
499+
500+ ### Constraints to respect
501+
502+ - ** Exactly one skill.** The ` skills ` CLI demands an explicit ` @selector ` when a host advertises
503+ more than one, which would break the bare ` npx skills add https://hopper.workleap.design ` .
504+ - ** A 5.5 MB budget** (` maxTotalBytes ` ). The build fails above it. Trim an entry rather than raising
505+ the ceiling. The heavy things deliberately left out are ` components/api/full ` (7 MB),
506+ ` llms-full.md ` , every ` */index.md ` merge artifact, and ` changelogs.md ` .
507+ - ** ` references/tokens/maps/** ` , ` references/icons/data.json ` and
508+ ` references/styled-system/unsafe-props-data.json ` must keep their AI docs paths.** The bundled
509+ scripts reach them through the generated ` files ` index, so renaming them breaks the scripts
510+ silently. The whole ` tokens/maps ` tree ships for the same reason: ` getTokenMapFiles ` resolves a
511+ file per category, so dropping the per-category files would make every narrower ` --category `
512+ throw.
513+ - ** ` /.well-known/skills/** ` is aliased onto ` public/agent-skills ` in two places, and both are
514+ needed.** ` netlify.toml ` ` [[redirects]] ` (with ` force = true ` ) handles production on the CDN;
515+ the ` beforeFiles ` rewrite in ` next.config.js ` handles ` next dev ` , which is what makes
516+ ` npx skills add http://localhost:3000 ` work. ` @netlify/plugin-nextjs ` does not honour
517+ ` beforeFiles ` for these paths — without the Netlify rules, ` /.well-known/skills/**/*.md ` is
518+ claimed by the ` /:path*.:ext(txt|md) ` rule in ` afterFiles ` and the ` /txt ` handler answers 400.
519+ CORS headers are likewise declared in both files, since Next's ` headers() ` does not apply to a
520+ CDN-resolved rewrite.
521+
522+ ### Testing a change
523+
524+ ` pnpm --filter=docs test ` runs ` checkSkillsConfig.ts ` , which asserts every ` from ` pattern still
525+ corresponds to a route in ` ai-docs.config.tsx ` , that templates and script entry points exist, and
526+ that each index section has content. It needs no build output and runs in PR CI.
527+
528+ Then, from a scratch directory outside the repo:
529+
530+ ``` bash
531+ pnpm doc:start
532+ cd $( mktemp -d) && npx skills add http://localhost:3000
533+ ```
534+
535+ Install from a scratch directory — running it inside ` wl-hopper ` would write into ` .claude/skills ` ,
536+ which is where this repo's own authoring skills live.
0 commit comments