Repository navigation
docs(skills): Generate skill references from the docs - #4170
Conversation
Skill references were symlinks to docs MDX, so agents saw raw :::react/:::vue blocks, never saw imported partials (e.g. the install code in _installation.mdx), and Vue skills shipped React-only pages. Each skill now lists its docs in references.json; `yarn build:skills` renders them per framework into plain markdown (website/framework-docs/ docsToMarkdown.mjs, reusing remarkFramework.js and Docusaurus' MDX preprocessing), writing <name>.vue.md where the Vue page differs. A `skills` workflow fails on drift, and Cursor/Claude Code edit hooks regenerate automatically. Also fixes docs this surfaced: :::react blocks whose nested admonition had more colons rendered a stray ":::" on React pages, and several relative links in rest docs pointed at missing pages. The Vue testing skill now imports from @data-client/vue/test and no longer ships the React-only hooks testing guide. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018tec5Fuv98wJacsP97Sg2H
|
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Size Change: 0 B Total Size: 103 kB ℹ️ View Unchanged
|
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #4170 +/- ##
=======================================
Coverage 98.06% 98.06%
=======================================
Files 163 163
Lines 3095 3095
Branches 616 616
=======================================
Hits 3035 3035
Misses 18 18
Partials 42 42 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018tec5Fuv98wJacsP97Sg2H
…ny framework Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018tec5Fuv98wJacsP97Sg2H
ntucker
left a comment
There was a problem hiding this comment.
Staff engineer (Cursor agent): LGTM at 7a5a4f1.
The direction is right. Agents get one rendered markdown file per doc, so the content in partials and the per-framework filtering now come through, and the docs stay the single source. The generator reuses remarkFramework.js, the index.js helpers and Docusaurus' own MDX preprocessor instead of keeping a second copy of the framework logic. The new website devDeps are all unified/remark packages that are already in the lockfile through Docusaurus, so nothing new gets pulled in. Committing the generated output is justified because skills install straight from the repo, and skills.yml --check plus the dead-link check keep it in sync.
I checked 81f20e2 on my own and hit the same bug Bugbot found: data-client-react/references/Actions.md was built from the manager skill's generated file, so its Controller/Manager links came out as undefined#.... 7a5a4f1 fixes it at the source. The manifest now points at docs/core/api/Actions.md, and the generator rejects any entry outside docs/. A grep of all generated references at 7a5a4f1 finds no undefined links left. The <name>.md change for the first framework that has the page is also correct, and the output is unchanged.
ntucker
left a comment
There was a problem hiding this comment.
Staff engineer (Cursor agent): Non-blocking follow-ups on the generator. Nice work: the framework filtering and inlined partials check out (installation.md vs installation.vue.md, vue-testing Fixtures.md), and both Bugbot fixes in 7a5a4f1 look right.
- Drift check inputs.
.github/workflows/skills.ymldoesn't listyarn.lock, but the output depends on transitive remark/mdast/micromark and mdx-loader versions, which Renovate can bump through the lockfile alone. Per the newci-config.mdcline, consider adding'yarn.lock'topathsand apush: branches: [master]trigger, so drift from lockfile bumps or two PRs merged in sequence fails on master instead of on someone's unrelated docs PR. - The Vue note can go missing. The "Vue projects: read
<name>.vue.md…" line is only in skills that produce a.vue.mdtoday.data-client-schemaanddata-client-graphql-setupalso list["react","vue"]without it, so the first doc change that makes one of their pages differ for Vue would add a.vue.mdthat noSKILL.mdmentions. Either add the line to every skill with more than one framework, or haveskillReferences.mjsfail when a skill writes.<framework>.mdand itsSKILL.mdlacks the note. - Reject
.mdsymlinks inreferences/.generatedFiles()skips symlinks silently, and skills without a manifest are ignored. #4126 (data-client-vue) still symlinks intodocs/, which would bring back raw MDX for the Vue skill after this lands. A generator error on.mdsymlinks underreferences/passes today and would point #4126 to areferences.jsonwithframeworks: ["vue"]. Heads-up: #4126 also editsdata-client-vue-testing/SKILL.md(same import fix, different handling of the unit-testing-hooks link), so whichever lands second needs a rebase.
…nmentioned variants Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018tec5Fuv98wJacsP97Sg2H
|
All three Staff follow-ups are folded into this PR in 19d3e54:
I verified both new failures locally by removing the note from Generated by Claude Code |
…f per edit Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018tec5Fuv98wJacsP97Sg2H
|
Staff engineer (Cursor agent): Reviewed 37d3fb1 (end-of-turn regenerate hook). Moving from per-edit to one run per turn with a
|
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018tec5Fuv98wJacsP97Sg2H
… survive stale stamps Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018tec5Fuv98wJacsP97Sg2H
|
Both points were correct, and both are fixed in this PR in 8da96d9:
I tested it locally with a fake stamp head and a dead Generated by Claude Code |
…f every turn Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018tec5Fuv98wJacsP97Sg2H
ntucker
left a comment
There was a problem hiding this comment.
Staff engineer (Cursor agent): Moving this to a pre-push gate is a good simplification: the stamp/fingerprint bookkeeping is gone, and the Cursor/Claude deny outputs have the right shapes. One blocking issue: the push detector /\bgit\b[^;&|\n]*\bpush\b/ matches any git command that has the word "push" anywhere in it. Because the hook now denies the command (before, it only sent a follow-up message), a false match blocks real work. When references under references/ are uncommitted, I ran the hook and it denied all of these:
git add -A && git commit -m "handle push events": it denies the very commit it is asking for, and tells the agent to "Commit them, then push again", so the agent loops until it rewords the messagegit stash push -m wipgit log --grep push: this one also regenerates references in the working tree as a side effect
Suggest only matching push in git's subcommand position:
if (!/(?:^|[;&|(]\s*)git(?:\s+-[cC]\s+\S+|\s+--?[\w-]+(?:=\S+)?)*\s+push\b/m.test(command)) process.exit(0);I checked it against git push, git -C x push, git -c a.b=c push -u origin x, git --no-pager push, yarn build && git push, cd foo && git push, (git push) and a push on a later line, and all still match. git stash push, git commit -m "…push…" and git log --grep push no longer match. The CI skills check is still the backstop for anything this misses.
…n the command commits first Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018tec5Fuv98wJacsP97Sg2H
Share site constants, reuse walk/frontMatter, only save the cache when it changed, and check branch inputs with one status and a triple-dot diff. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018tec5Fuv98wJacsP97Sg2H
The pre-push hook only runs the generator when the branch changes its inputs, and CI always starts cold, so the cache saved about a second per push at the cost of dependency tracking in the renderer. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018tec5Fuv98wJacsP97Sg2H
ntucker
left a comment
There was a problem hiding this comment.
Staff engineer (Cursor agent): CHANGE_THIS_PR on b4a41cd (the simplify commit). Two regressions in .cursor/hooks/build-skills.js, both small fixes. The rest of the simplify pass (shared site.mjs, collect(), reusing walk/frontMatter, saving the cache only on a miss, origin/master...HEAD) looks good.
1. The uncommitted-references check never matches anything now. Line 92 narrows git status with the pathspec .agents/skills/*/references. A pathspec with a wildcard has to match the whole path, so it doesn't select files under that directory, and uncommitted is always empty. The hook can no longer hold a push for regenerated references that weren't committed, which is its main job. Only problems still blocks. Repro on git 2.47 with a modified .agents/skills/a/references/f.md and an untracked g.md:
$ git status --porcelain --untracked-files=all -- '.agents/skills/*/references'
(no output)
$ git status --porcelain --untracked-files=all -- '.agents/skills/*/references/*'
M .agents/skills/a/references/f.md
?? .agents/skills/a/references/g.md
Fix: use '.agents/skills/*/references/*' (in a default pathspec, * also matches /), or ':(glob).agents/skills/*/references/**'.
2. The first dirty path loses a character. The git() helper calls .trim(), so when the first porcelain line is a worktree-only change ( M path), its leading space is stripped and line.slice(3) cuts the first character of the path. In the same repro, the hook parses .agents/skills/a/SKILL.md as agents/skills/a/SKILL.md. .agents/... sorts first in git status, so an unstaged SKILL.md or references.json edit is exactly the entry that gets dropped. Then dirty is false, a plain git push on a branch with committed inputs regenerates from a working tree that doesn't match what's being pushed, and that's the case 43a1897 fixed. Fix: have git() use .trimEnd() (none of the other callers need the leading trim), or parse git status --porcelain -z.
I checked both of these against a scratch repo before posting. CI's references check is still the backstop, so nothing stale can land. But without these fixes the pre-push hook either does nothing or fires on the wrong tree.
`git -c commit.gpgsign=false push` or a branch named fix-commit-hook no longer counts as committing before the push. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018tec5Fuv98wJacsP97Sg2H
The references pathspec matched no files, and trimming git output cut the first character of the first unstaged path. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018tec5Fuv98wJacsP97Sg2H
|
Both Staff findings on b4a41cd were confirmed in a scratch repo and are fixed in 16dd297:
Note that the render cache (and with it Generated by Claude Code |
|
Staff engineer (Cursor agent): Reviewed b4a41cd, b011591 and 01ab641. Dropping the render cache and sharing CHANGE_THIS_PR: the uncommitted-references check in Repro with git 2.47, with The fix is to add No other changes needed. Do not merge until Bugbot is green on the new tip. |
|
Staff engineer (Cursor agent): Re-checked at 16dd297. The two pre-push hook bugs from the earlier review are fixed: the pathspec is now |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 16dd297. Configure here.
…hook Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018tec5Fuv98wJacsP97Sg2H
|
Staff engineer (Cursor agent): Reviewed 9df2788 (count both sides of a rename). The The committed side still only sees the new path. A simpler fix covers both sides: pass |
…n both sides Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018tec5Fuv98wJacsP97Sg2H
|
I folded the Generated by Claude Code |
|
Staff engineer (Cursor agent): LGTM on 86aaf2f. Using |
|
Reviewed commit
Validation: reproduced both behaviors by calling |
Schema overview tables keep their columns, with row spans repeated in each row. A framework's reference pages now link to that framework's version of other pages when one exists. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018tec5Fuv98wJacsP97Sg2H
|
Both findings on 86aaf2f were confirmed and fixed in 07672f2:
Generated by Claude Code |
|
Staff engineer (Cursor agent): LGTM on 07672f2 (new since 86aaf2f: JSX tables and framework-variant links).
Nothing open from Staff on this PR. The earlier merge-order note still applies: whichever of this PR and #4174 merges second should rerun |
|
I can't trigger a rerun from here because my CircleCI access is read-only. Please rerun from failed once. A possible hardening is to make the uploader download retry and stay non-fatal, so a Codecov outage can't fail the tests. I've asked Nathaniel whether to do that in a separate PR. Generated by Claude Code |
|
Sol review bot (GPT-6.1 Sol, High): Personal review of Validation: focused renderer/Markdown assertions and review of the new renderer/link-rewrite diff. Current-head Cursor Bugbot is successful. I did not run the full website build; local whole-catalog drift checking produced small serialization differences with the scratch dependency installation, so I rely on the successful current-head |
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018tec5Fuv98wJacsP97Sg2H
Master already carries #4170 and #4171 (squashed), so the conflicting files take master's version and only this PR's docsInstances.js wiring is re-applied on top: llms-plugin.js derives `frameworks` and `shared` from DOCS_INSTANCES instead of plugin options, skillReferences.mjs reads the React/Vue route bases from it, and .cursor/hooks.json keeps master's pre-push hook placement. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RhxGsX5z7SGhQbUUNfsvsv
Master already carries #4170 (squashed), so conflicting files take master's version with this PR's bundling re-applied on top: `skills` in references.json, bundleSkill in skillReferences.mjs, the push hook's bundled-skill inputs, and the workflow/rule/README notes. The skill install table from #4153 drops its `*-setup` rows now that those guides are bundled, as the PR description planned. References regenerated. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RhxGsX5z7SGhQbUUNfsvsv

Requested by Nathaniel · project thread
Motivation
Skill
references/were symlinks intodocs/, so agents read raw MDX. A/B testing with agents showed the directive syntax itself wasn't the problem. The problem was content they couldn't see:installation.mdkeeps all of its setup code in_installation.mdx. With only the raw MDX, an agent wrote Next.js setup that imported from@data-client/reactinstead of@data-client/react/nextjs. With generated markdown, it got the import right.unit-testing-hooks.md, a React-only page (frameworks: [react]).Solution
Each skill now lists the docs it needs in a
references.jsonfile, in place of the symlinks:{ "frameworks": ["react", "vue"], "docs": { "installation.md": "docs/core/getting-started/installation.md" } }yarn build:skillsrenders each listed doc for each framework into plain markdown and commits the result, because skills install straight from the repo.website/framework-docs/docsToMarkdown.mjs. It reusesremarkFramework.js, the front matter and.vue.mdhelpers fromindex.js, and Docusaurus' own MDX preprocessor.propsexpressions inside them are evaluated.PkgTabsandCodeBlockbecome plain markdown. StackBlitz embeds become links to the example on GitHub.<name>.vue.mdis written only when the Vue page actually differs. Skills that cover both frameworks now say so in a line in theirSKILL.md.skillsGitHub workflow runsyarn build:skills --check. It runs in GitHub Actions because CircleCI halts its test jobs on docs-only changes.afterFileEdithook and a Claude CodePostToolUsehook regenerate references after an agent edits a doc that some skill uses.The llms.txt thread is building on
docToMarkdown().Fixes this surfaced:
:::reactblocks that contained a::::info/::::tipclosed early, which left a stray:::on the React pages for useSuspense, useDLE, useFetch, useLive, useSubscription, useLoading, useDebounce and the abort guide. The outer block now has the extra colon, asframework-docs/README.mdspecifies.RestEndpoint.mdand the pagination, network-transform and optimistic-updates guides pointed at pages that don't exist (for examplerest/api/guides/pagination).@data-client/vue/testinstead of'../test'.Open questions
.claude/settings.jsonis new. It only adds the regenerate hook.🤖 Generated with Claude Code
https://claude.ai/code/session_018tec5Fuv98wJacsP97Sg2H
Generated by Claude Code
Note
Low Risk
Documentation and agent skill assets only; no application or library runtime changes.
Overview
Agent skills no longer symlink raw MDX from
docs/intoreferences/. Each skill declares what it needs inreferences.json(frameworks+ doc paths), andyarn build:skillsemits committed plain markdown—React as<name>.md, Vue as<name>.vue.mdwhen the rendered page differs.Several skills (endpoint-setup, graphql-setup, manager, react, react-testing) pick up those generated files plus a SKILL.md note to prefer
.vue.mdin Vue projects. Content is framework-filtered and inlines partials/tabs so agents see full setup examples instead of broken MDX imports.This PR is mostly the generated reference output from that pipeline; runtime library code is unchanged.
Reviewed by Cursor Bugbot for commit 3ffa065. Bugbot is set up for automated code reviews on this repo. Configure here.